您的位置:首页 > 其它

HDU 1171 Big Event in HDU (多重背包,可转换为01背包)+对于背包的一点认识 4000

2017-10-09 20:28 465 查看
传送门HDU 1171




题目大意:有n(n<=50)种东西,告诉你每种东西的价值val(val<=50)和数量num(num<=100),现在要你把这些东西分为两组,使得两组的价值总和之差尽量小。求两组的价值和,大者在前。

前置技能:我默认读这篇文章的读者已经了解过了01背包、完全背包和多重背包了。如果没有也没关系,给推荐个网站:背包九讲。




再来稍微谈下多重背包的思路,当一种物品的数量足够多,以至于总重量比背包的容量都大的话,相当于这种物品的数量有无穷多个,这时候可以转化为完全背包求解。

相反,当一种物品的数量不够多的时候,可以把这一堆同样的物品拆分成多个单个的物品,这时候就可以用01背包求解了。但是呢,这样拆分的话复杂度太高了,所以有另一种方式,我称之为倍增法,先拆出1个,如果剩下的还能拆再拆出2个(这2个看作是一个物品),如果剩下的还能拆再拆出4个(这4个看作是一个物品)……依次类推拆出2的次幂个。如果不够了,那么剩下的就是一个物品。

例如有13个某种物品,先拆出1个,还剩12个,再拆出2个,剩余10个,再拆出4个,剩余6个,再拆出8个,发现不够了,所以剩下的6个看作一个物品。这样本来13个物品被拆成了4个物品,时间复杂度大大降低。





思路:我们假设物品总价值为sum,我们发现,最优的情况是两个分组各占一半,稍差一点的情况是一者不到一半,另一者比一半多。所以我们就可以看看一个容量为 sum/2 的背包能装多满。因为每个物品有固定的数量,这就是多重背包问题。有人可能会说,这里只有价值,没有背包的容量啊,其实题目做多了你可以发现,只有一个值的时候价值和重量是可以共用的。

下面提供两种思路:

转换为01背包问题:01背包就是有n种东西,要么取,要么不取,因为最多一共有50*100=5000件物品,所以可以把每个价值相同的物品拆成单个物品,然后用01背包求解。这种思路简单,代码也少,但是时间复杂度高,如果数据再大点就不适用了。

直接用多重背包解决:也就是用所有的物品往一个容量为 sum/2 的背包里装,装到最满顶多一半,这时候的值就是少者的值。



上图上面的是用01背包思路做的,下面的是用多重背包思路做的,可以发现用多重背包要快很多,当然了,代码会稍微多点,不过直接套模板就可以了。

注意:输入时当n<0时退出,也不可以写成 n==-1 时退出,否则会答案错误或者超时。

先给出01背包的代码:

#include<stdio.h>
#include<string.h>

int val[5010],dp[250010];

int max(int a,int b)
{
if(a>b) return a;
else return b;
}

int main()
{
int i,j,n,a,b,v,sum,tol;
while(~scanf("%d",&n)&&n>=0)
{
tol=0; //下标
sum=0; //总价值
for(i=1;i<=n;i++)
{
scanf("%d%d",&a,&b);
sum+=a*b;
while(b--) val[tol++]=a; //拆成单个
}
v=sum/2; //一半的容量
memset(dp,0,sizeof(dp));
//01背包核心代码
for(i=0;i<tol;i++)
for(j=v;j>=val[i];j--)
dp[j]=max(dp[j],dp[j-val[i]]+val[i]);
printf("%d %d\n",sum-dp[v],dp[v]);
}
return 0;
}

然后是多重背包的代码,上面的部分可以直接用作模板,我测试过个3版本的多重背包,下面这个是最快的。当然还可能存在更快的也未可知……
#include<stdio.h>
#include<string.h>

int n,v,dp[250010];

int max(int a,int b)
{
if(a>b) return a;
else return b;
}

//01背包,当前物品价格、重量
void  ZOP(int prz,int wt)
{
int j;
for(j=v;j>=wt;j--)
dp[j]=max(dp[j],dp[j-wt]+prz);
}

//完全背包,当前物品价格、重量
void CP(int prz,int wt)
{
int j;
for(j=wt;j<=v;j++)
dp[j]=max(dp[j],dp[j-wt]+prz);
}

//多重背包,当前物品价格、重量、数量
void MP(int prz,int wt,int cnt)
{
//如果放不下全部,则转化为完全背包求解
if(wt*cnt>=v) CP(prz,wt);
else //否则,转化为01背包求解
{ //将cnt个相同物品分多组,每组1,2,4,8…个
//类似于倍增法,最后剩下的可能不是2的次幂
int k=1;
while(k<cnt)
{ //k个物品的价值和重量分别为 k*prz,k*wt
ZOP(k*prz,k*wt);
cnt-=k;
k<<=1;
}
if(cnt>0) ZOP(cnt*prz,cnt*wt);
}
}

int main()
{
int i,sum,val[55],num[55];
while(~scanf("%d",&n)&&n>=0)
{
sum=0; //总价值
for(i=1;i<=n;i++)
{
scanf("%d%d",&val[i],&num[i]);
sum+=val[i]*num[i];
}
v=sum/2; //一半的体积
memset(dp,0,sizeof(dp));
//多重背包求解
for(i=1;i<=n;i++)
MP(val[i],val[i],num[i]);
printf("%d %d\n",sum-dp[v],dp[v]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: