多重背包的 二进制优化 / 转化为有限制的完全背包

多重背包:有n种物品,每个物品的重量为w[i],每个物品的价值为h[i],每种物品有c[i]个。

最朴素的做法中,我们把c[i]个物品i看成c[i]个不同的物品,进而转化成了0-1背包。然后在0-1背包的基础上我们还可以进行二进制优化

二进制优化

我们知道:
20,21,22,23,,,2n可以组成1~2(n+1)-1中的任意数(每个数只能用一次)
所以我们可以把c[i]个相同的物品,看成这样的几堆物品:
10=1+2+4+3
15=1+2+4+8
36=1+2+4+8+16+5
后面的4、4、6(Olog(n))堆物品就可以代替前面的10、15、36(O(n))堆,因为他们可以组成1~c[i]的任意数,复杂度也就在这里降低了

for(int i=1; i<=n; i++)
{
    for(int k=1; k<=c[i]; k<<=1)
    {
        for(int j=v; j>=k*w[i]; j--)
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*h[i]);
        c[i]-=k;
    }
    if(c[i])
       for(int j=v; j>=c[i]*w[i]; j--)
            dp[j]=max(dp[j],dp[j-c[i]*w[i]]+c[i]*h[i]);
}

转化为有限制的完全背包

0-1背包中我们为了保证每件物品只参与了一次,在第二层循环中我们是从后往前遍历for(j=v; j>=w[i]; j–)。但在完全背包中,每件物品都可以用无数次,所以直接从前往后遍历一遍就可以for(j=w[i]; j<=v; j++)。他们的复杂度都是O(n*v)(两层循环)

多重背包中,每件物品的个数是有限的,在转化成0-1背包以及它的二进制优化中,我们另外加了一层循环来限制每件物品的数量。那么我们如果想将多重背包转化为完全背包,又怎么保证结果中每件物品使用的数量不会超过c[i]呢?————我们可以用一个数组cou[]来记录当前物品使用的次数。当我们发现当前物品的使用数量超过c[i]时退出循环就可以了。

例题:http://acm.hdu.edu.cn/showproblem.php?pid=1059
题意:玛莎和比尔有一堆弹珠,他们想把弹珠分开,一人一份。如果弹珠的大小一样就好了,那样直接一人一半。不幸的是,这些弹珠大小不一。所以他们俩就给每个弹珠编了一个号,从1到6,现在他们想把这些弹珠分成两份,只要他们最后得到的总值一样就可以。然而问题又来了,因为他们发现如果弹珠是1、3、4、4这种情况,他们任然无法平分。所以现在他们想让你确定一下他们这堆弹珠是否能够被平分。
代码:

#include
#include
#include
typedef long long ll;
const int maxn=1e4+100;
int a1,b,c,d,e,f,k=1,dp[101000],cou[100010];
int main()
{
    while(scanf("%d%d%d%d%d%d",&a1,&b,&c,&d,&e,&f),a1||b||c||d||e||f)
    {
        int sum=a1+b*2+c*3+d*4+e*5+f*6;
        int a[10];
        a[1]=a1,a[2]=b,a[3]=c,a[4]=d,a[5]=e,a[6]=f;
        if(sum%2)
        {
            printf("Collection #%d:\nCan't be divided.\n\n",k++);
            continue;
        }
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(int i=1; i<=6; i++)
        {
            //memset(cou,0,sizeof(cou));
            for(int q=0;q<=sum/2;q++)
                cou[q]=0;//每次都要初始化为0
            for(int j=i; j<=sum/2; j++)
            {
                if(cou[j-i]>=a[i])
                    break;
                if(!dp[j]&&dp[j-i])
                {
                    dp[j]=1;
                    cou[j]=cou[j-i]+1;
                }
                if(dp[sum/2])
                    break;
            }
        }
        if(dp[sum/2])
            printf("Collection #%d:\nCan be divided.\n\n",k++);
        else
            printf("Collection #%d:\nCan't be divided.\n\n",k++);
    }
    return 0;
}

其他例题:Coins

你可能感兴趣的:(dp)