hdu 1059 Dividing(二进制转化优化) 分组背包

多重背包问题
题目
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表示也就是说选择12对应的背包相当于选择系数为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啊, 然后3for循环,最坏的复杂度就是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;

}

大家复制下来看吧 不知道为什么老是我复制的被挡住


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值