n个物品 背包容量W
w[i] 费用,v[i] 价值;
01背包
每件物品选或不选。
板子:
void zerone(int wei,int val){
for(int j=W;j>=wei;j--) d[j]=max(d[j],d[j-wei]+val);
}
for(int i=1;i<=n;i++)
zerone(w[i],v[i])
超大01背包
每个物品只能拿一次,物品数量M<=100,背包容量T<=10^9,每个物品的价值<=100,每个物品的重量<=10^9
将dp数组第二维变为价值,存储达到价值j所需的最小重量
dp[i][j]=min(dp[i][j],dp[i-1][j-v[i]]+w[i])
在dp[n][j]<=W里找最大的j
完全背包
每件物品可以选无数次。
void wanquan(int wei,int val){
for(int j=wei;j<=W;j++) d[j]=max(d[j],d[j-wei]+val);
}
for(int i=1;i<=n;i++)
wanquan(w[i],v[i])
数竞大佬的证明思路
①dp[i][j]max=dp[i-1][j-k*w[i]]+k*v[i] 0<=k
②dp[i][j]max=dp[i-1][j-w[i]-t*w[i]]+t*v[i]+v[i] 0<=t,1<=k
dp[i-1][j] k==0
③dp[i][j-w[i]]max=dp[i-1][j-w[i]-t*w[i]]+t*v[i] 0<=t
将③带入②
dp[i][j]max = dp[i-1][j]
dp[i][j-w[i]]+v[i]
多重背包
每件物品可以选固定数量次。
二进制拆分
void duochong(int wei,int val,int num){
if(num*wei>=W) {
wanquan(wei,val);
return;
}
int k=1;
while(k<num){
zerone(wei*k,val*k);
num-=k;
k*=2;
}
zero(wei*num,val*num);
return ;
}
for(int i=1;i<=n;i++)
duochong(w[i],v[i],m[i])
时间复杂度 m*xigema(logk)
二进制拆分的可行性证明(不严格):
k=2^0+2^1+2^2+………2^x+p;
k=(111…….1)(2进制)+p;
那么从0–1111111….1的数,可由2的次幂的选否表示出来。
p<11111…..11(p<2^(x+1)),p+111…111=k;
从11111…..1+1—–k,固定选p,那么要凑的质量-p一定小于1111..1,
有上述结论可知,这个数是可以凑出来的。
证毕;
混合背包
每个物品可以选一件,无数件或给定数量件
解法:对于每件物品,判断是01,完全,多重,分别计算。
for(int i=1;i<=n;i++){
if(wanquan) wanquan(w[i],v[i]);
if(01) zerone(w[i],v[i]);
if(duochong) duochong(w[i],v[i],m[i]);
}
二维费用背包
对于每个物品,有两种费用,每个费用都有最大值;
隐含方式:
最多取m件,这是件数就是另一个费用,每个物品此费用为1
for(int i=1;i<=n;i++){
for(int j=W;j>=w[i];j--){
for(int k=U;k>=u[i];k--){//另一个价值u
dp[j][k]=max(dp[j][k],dp[j-w[i]][k-u[i]]+v[i]);
}
}
}
完全jk顺序循环,多重拆分;
分组背包
所有物品分为k组,每组中至多选择一个物品;
for(int i=1;i<=k;i++) //每组内部
for(int j=W;j>=0;j--)//背包总重量
for(p)//p是该组内部的物品
dp[j]=max(dp[j],dp[j-w[p]]+v[p]);
正确性证明:
一开始以为只要背过代码就可以,结果度娘一搜,发现背包九讲第一版中居然有错误。
http://www.cppblog.com/Onway/archive/2010/08/09/122695.html
%这位不知名大神。
递推式dp[j]=max(dp[j],dp[j-w]+v]
循环顺序 组别,质量,组内物品。
对于每个组内,对每一个质量进行循环,由于j是逆序,dp[j]是上一组决策的结果。
同样由于j是逆序,质量为j-w的背包还未在(第一层循环)本组中决策,故j-w也是上一组决策的结果,保证滚动数组的正确性。