多重背包:有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<stdio.h>
#include<string.h>
#include<algorithm>
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