• 入门多重背包-HDU2844


    题目链接

    给你若干种面值的硬币,每种硬币有多个,给你一个数字m,问你能用给你的硬币组合出和为1-m之间的几种情况,例如1个两元硬币 2个一元硬币 m=3时 就能组合出来1 2 3 答案就是三 ;m=4时,答案就是4 ;m=5时答案还是4。(难得我解释了一次题意,hh)

    如果会01背包与完全背包,多重背包其实看代码就能理解,难的地方只有一个就是那个二进制优化,昨晚问马姐姐问到十一点多终于懂了,感谢马姐姐,马仙女~

    多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

    f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]  |  0<=k<=n[i]}

    多重背包代码:

    void pd(int m,int v,int w,int num){   //m钱数
        if(num*v>=m){
            pall(m,v,w);
            return;
        }
        for(int i=1;i<=num;i<<=1){
            p01(m,i*v,i*w);
            num-=i;
        }
        if(num) p01(m,num*v,num*w);
    }

    其实也没多长对吧,别的不解释,就只说说二进制优化吧

    再说二进制优化之前,我们要先明白笨方法是什么,这里给出

    笨方法的代码:

    void pd(int m,int v,int w,int num){
        if(num*v>=m){
            pall(m,v,w);
            return;
        }
        for(int i=1;i<=num;i++){
            p01(m,i*v,i*w);
        }
    }

    首先我要你搞明白一件事,用这个笨方法,也能出结果,只是效率不高,我昨晚就是没弄明白这个,所以耽误了好久。

    之所以这总方法效率不高,在于,他对每一个i,也就是放置同种物品的个数,都从1到num遍历了,我们这道题物品个数i最大可能是1000的,可想而知,对于每一个i都要进行一次01背包,效率肯定不高,,那么为什么二进制优化中,只取了某几个i(比如说num=10 的时候,i只取了1 2 4 3这四个数)来进行,却能达到和“笨方法”一样的效果呢?

    显然,为了保证背包动归结果正确,我们要考虑在背包内放入1 , 2 3 4,,,num件某物品时的这num种情况所对应背包的状态,缺一不可。我们的笨方法就很直观的达到了这种目的,下面分析“聪明办法”为什么能达到这种目的:

    我们以10为例,第一轮01背包i=1,如果i=1也不行那后面i肯定也都不行说明放不进去

    如果i=1可以,下一次i=2时,经过了01背包中的状态转移方程:f[k]=max(f[k],f[i-v]+w) (k从m到v循环)  之后,对于每一个f[k]可能有三种情况,那就是:

    1,i=1时只放一个物品时的物品被拿出来了,放进去了两个物品

    2,i=1时放进去的物品没拿出来,又放进去两个物品

    3,i=1时放进去的物品没拿出来,i=2时的两个物品也没拿出来

    这三种情况可能不会都出现,如果没有出现,那就说明这种情况不会是解~

    以此类推,之后i=4 ,3

    可以把放进去 1 2 3 4 5 6 7 8 9 10个物品的情况统统考虑一遍,从而达到了和“笨方法”一样的效果。

    This is it.

    完整代码:

    #include <iostream>
    #include <cstring>
    #define INF 0x3f3f3f3f
    using namespace std;
    
    const int maxn=0x3f3f3f3f;
    int n,m;
    int f[100010],a[110],c[110]; 
    
    void p01(int m,int v,int w){
        for(int i=m;i>=w;i--){
            f[i]=max(f[i],f[i-w]+v);
        }    
    }
    
    void pall(int m,int v,int w){
        for(int i=w;i<=m;i++){
            f[i]=max(f[i],f[i-w]+v);
        }
    }
    
    void pd(int m,int v,int w,int num){
        if(num*v>=m){
            pall(m,v,w);
            return;
        }
        for(int i=1;i<=num;i<<=1){
            p01(m,i*v,i*w);
            num-=i;
        }
        if(num) p01(m,num*v,num*w);
    }
    
    int main()
    {
        freopen("in.txt","r",stdin); 
        while(cin>>n>>m){
            memset(f,0,sizeof(f));
            if(n==0) break;
            for(int i=1;i<=n;i++) cin>>a[i];
            for(int i=1;i<=n;i++) cin>>c[i];
            for(int i=1;i<=m+1;i++)   f[i]=-maxn;
            f[0]=0;
            for(int i=1;i<=n;i++){
                pd(m,a[i],a[i],c[i]);
            }
            int ans=0;
            for(int i=1;i<=m;i++){
                if(f[i]>0) ans++;
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    View Code
    柳暗花明又一村
  • 相关阅读:
    [iOS]C语言技术视频-15-指针变量高级用法练习一(函数指针完成动态排序)
    [iOS]C语言技术视频-14-指针变量高级用法(函数指针)
    [iOS]C语言技术视频-13-指针变量练习三(结构体指针变量)
    [iOS]C语言技术视频-12-指针变量练习二(数组打印)
    ReentrantLock加锁解锁过程
    spring初始化bean之后的3种回调方式
    spring循环引用-笔记
    二叉树的三种遍历方式
    快速排序
    java模拟实现单向链表
  • 原文地址:https://www.cnblogs.com/ucandoit/p/8808791.html
Copyright © 2020-2023  润新知