多重背包问题
题目
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本算法
转化为01背包问题
一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,
假如一种物品的体积为2数量为5我们可以把其存在一个数组里面存5次然后进行01背包中的操作
则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是O(V*∑n[i])。
但是当数据很大的时候这种方法可能会超时我们可以用状态压缩
可以考虑二进制的思想
方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示
例如3可以用1+2表示也就是说选择1和2对应的背包相当于选择系数为3对应的背包这样就可以弥补系数为3的背包不存在的情况
像假如一种物品的体积为2数量为5我们可以把他分为122而不是11111少了2个效率就高了背包少了2个
这样就将第i种物品分成了O(logn[i])种物品,将原问题转化为了复杂度为O(V*∑logn[i])的01背包问题,是很大的改进。
看明白了吧不明白的话要回去看看01和完全背包了下面给个例子:
hdu1059Dividing(二进制转化优化)
Dividing
TimeLimit:2000/1000MS(Java/Others)MemoryLimit:65536/32768K(Java/Others)
TotalSubmission(s):5887AcceptedSubmission(s):1594
ProblemDescription
MarshaandBillownacollectionofmarbles.Theywanttosplitthecollectionamongthemselvessothatbothreceiveanequalshareofthemarbles.Thiswouldbeeasyifallthemarbleshadthesamevalue,becausethentheycouldjustsplitthecollectioninhalf.Butunfortunately,someofthemarblesarelarger,ormorebeautifulthanothers.So,MarshaandBillstartbyassigningavalue,anaturalnumberbetweenoneandsix,toeachmarble.Nowtheywanttodividethemarblessothateachofthemgetsthesametotalvalue.
Unfortunately,theyrealizethatitmightbeimpossibletodividethemarblesinthisway(evenifthetotalvalueofallmarblesiseven).Forexample,ifthereareonemarbleofvalue1,oneofvalue3andtwoofvalue4,thentheycannotbesplitintosetsofequalvalue.So,theyaskyoutowriteaprogramthatcheckswhetherthereisafairpartitionofthemarbles.
Input
Eachlineintheinputdescribesonecollectionofmarblestobedivided.Thelinesconsistofsixnon-negativeintegersn1,n2,...,n6,whereniisthenumberofmarblesofvaluei.So,theexamplefromabovewouldbedescribedbytheinput-line``101200''.Themaximumtotalnumberofmarbleswillbe20000.
Thelastlineoftheinputfilewillbe``000000'';donotprocessthisline.
Output
Foreachcolletcion,output``Collection#k:'',wherekisthenumberofthetestcase,andtheneither``Canbedivided.''or``Can'tbedivided.''.
Outputablanklineaftereachtestcase.
SampleInput
101200
100011
000000
SampleOutput
Collection#1:Can'tbedivided.
Collection#2:Canbedivided.
此题属于多重背包,数据很大,需要二进制优化,不然超时。优化后直接当做01背包来做,01背包是逆序!
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1059
题目大意:
给你6个石子,石子的价值分别从1->t;6,然后输入各个石子的数量。之后要你判断石子能否分成两堆,使两堆石子的价值一样。想了好久,最后觉得才发现可以用多重背包做。
解题思路:
首先这6种石子不是无数个的,而且石子有价值,可以假设石子的体积和价值一样,反正题目都木有要求体积嘛。
一开始先算出石子的总价值,然后判断总价值的奇偶性,如果是奇数,肯定分不了,偶数的话就多重背包下。以石子的总价值的一半作为背包。初始化为负无穷,如果最后h_tal是总价值的一半,dp[h_tal]如果有数量的话,那么就证明这个价值可以达到,意思就是可以取石子取出价值为总价值的一半,那么就满足了题意要求的平均分为一半。
即如果能装满一半的背包那必然能剩下一半即能够把总的val分成两份
ok,总的思路就是这样了,思路相当的清晰啊,不过这样做下去,直接就TLE了,因为这那个个总价值最多是20000*6,这可是120000/2啊, 然后3个for循环,最坏的复杂度就是6*120000/2*20000.不超时才怪。
其实做个简单的处理就可以了,把各种石子的数量模10就行了,因为一种石子超过10,比如12,那么前面10个是肯定可以均分掉的,所以就 不考虑它了,直接考虑2就行了。。
也可以二进制压缩看下面压缩代码 如果看不懂 最下面有我的代码 并且有解释
代码:
Cpp代码
1#include<iostream>
2#include<stdio.h>
3#include<memory.h>
4usingnamespacestd;
5
6constintN=6;
7intV,total,sum;
8intbag[120005];
9intw[15],n[15],v[1005];
10
11void_01_bag()
12{
13inti,j;
14memset(bag,0,sizeof(bag));
15for(i=0;i<total;i++)
16{
17for(j=sum;j>=v[i];j--)
18{
19bag[j]=max(bag[j],bag[j-v[i]]+v[i]);//令其体积和价值相同
20}//别忘记了上面的j是>=v[i]别忘记等号哈
21}
22}
23
24intmain()
25{
26inti,temp,zz=1;
while(scanf("%d%d%d%d%d%d",&n[0],&n[1],&n[2],&n[3],&n[4],&n[5]))
27{
28if(n[0]+n[1]+n[2]+n[3]+n[4]+n[5]==0)break;
29printf("Collection#%d:\n",zz++);
30V=0;
31for(i=0;i<N;i++)
32{
33w[i]=i+1;
34V+=w[i]*n[i];//求总和
35}
36if(V%2==1)//总和是奇数则不能平分
37{
38printf("Can'tbedivided.\n\n");
39continue;
40}
41sum=V/2;total=0;
42for(i=0;i<N;i++)//二进制压缩为//——01背包
43{
44if(n[i]==0)continue;
45temp=1;
46while(n[i]>temp)
47{
48v[total++]=temp*w[i];//将新的值//赋给v[]
49n[i]-=temp;
50temp*=2;
51}
52v[total++]=n[i]*w[i];
53}
54_01_bag();//用新的v[]数组直接拿来01背包
55if(bag[sum]!=sum)
56printf("Can'tbedivided.\n\n");
57else
58printf("Canbedivided.\n\n");
59}
60
61return0;
62}
-----------------------------------我的代码---------------------------------------
-----------------------------------------------------------------------------------------------------------------------
/*我们可以把总价值分出来一半 然后把物体的价值和体积看成一样 用体积为总价值一半的背包放这些物体 如果能够放满这个背包 说明有
办法分出来一半放进背包里面 这样就分成了两份
*/
#include<stdio.h>
#include<string.h>
int b[10000];// 存的是二进制压缩后的各个物体的价值
int c[120005];//这个要大啊 因为下面还有c[val]而val是总价值的一半所以很大
int bag(int val,int n)//它是01背包
{
int i,j;
memset(c,0,sizeof(c));
for(i=0;i<n;i++)//控制物体的个数 一个一个的解决 即加上这个物体后下面将列出所有的能装下的情况
for(j=val;j>=b[i];j--)//在各个已装体积下 腾出b[i]的空间把b[i]放进去 注意是各个体积下
{ c[j]=c[j]>c[j-b[i]]+b[i]?c[j]:c[j-b[i]]+b[i];//比较一下是放b[i]好 还是不放好
//printf("%d %d\n",c[val],val);}
if(c[val]==val) return 1;
else return 0;
}
int main()
{
int a[10],i,cnt=1,sum_v,val,temp,j;
while(1)
{
scanf("%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4],&a[5]);
sum_v=a[0]+a[1]*2+a[2]*3+a[3]*4+a[4]*5+a[5]*6;
if(sum_v==0) return 0;
printf("Collection #%d:\n",cnt++);
if(sum_v%2==1) {printf("Can't be divided.\n\n"); continue;}
val=sum_v/2;j=0;
for(i=0;i<6;i++)
{
if(a[i]==0) continue;
temp=1;
while(a[i]>temp)
{
b[j++]=temp*(i+1);
a[i]=a[i]-temp;
temp=temp*2;
}
b[j++]=a[i]*(i+1);//注意这里哈 千万不要写成temp 一开始就一个劲错这里
}
if(bag(val,j)) printf("Can be divided.\n\n");//如果能够分出一个能装下一半价值的背包
else printf("Can't be divided.\n\n");//那么剩下的是另一半 正好分成了2份
}
return 0;
}
大家复制下来看吧 不知道为什么老是我复制的被挡住