多重背包
问题描述:给定nnn种物品和一个体积为VVV的背包,第iii种物品数量为mim_imi,体积为cic_ici,价值为wiw_iwi。如何装填背包使总价值最大?
通过直接求解,转移方程式:dp[i][j]=max(dp[i−1][j],dp[i−1][j−k×c[i]]+k×w[i]),k∈[1,min(m[i],jc[i])]dp[i][j]=\max(dp[i-1][j],dp[i-1][j-k\times c[i]]+k\times w[i]),k\in[1,\min(m[i],\frac{j}{c[i]})]dp[i][j]=max(dp[i−1][j],dp[i−1][j−k×c[i]]+k×w[i]),k∈[1,min(m[i],c[i]j)]。复杂度O(V∑i=1nmi)O(V\sum\limits_{i=1}^n m_i)O(Vi=1∑nmi),超时。
实际上,多重背包属于0/10/10/1背包的推广,易得其可转换为0/10/10/1背包问题:将第iii种物品视为mim_imi种独立(不同)的物品,并按0/10/10/1背包求解。定义状态数组dp[i][j]dp[i][j]dp[i][j],表示将前iii个物品放入容积为jjj的背包时的最大价值。实际上复杂度不变,仍为O(V∑i=1nmi)O(V\sum\limits_{i=1}^n m_i)O(Vi=1∑nmi),超时。
int dp[n+1][V+1],c[n],w[n],m[n];
int MultiplePack(){
for(int i=0;i<=n;i++) dp[i][0]=0;
for(int i=0;i<=V;i++) dp[0][i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=V;j++)
for(int k=1;k<=m[i]&&k*c[i]<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+k*w[i]);
return dp[n][V];
}
二进制优化
原理:倍增。任意十进制整数均可使用2的幂次经过有限次相加得到,以2i(i∈[0,⌈log2mi⌉+1])2^i(i\in[0,\lceil\log_2m_i\rceil+1])2i(i∈[0,⌈log2mi⌉+1])顺次拆分,最后可能有一个余数。因此使用倍增即可将第iii种物品变为log2mi\log_2m_ilog2mi个,每个物品体积为2k×ci2^k\times c_i2k×ci,价值为2k×wi2^k\times w_i2k×wi。
以下为二进制拆分代码,之后使用new_n
、new_c
、new_w
以0/10/10/1背包求解即可。复杂度O(V∑i=1nlog2mi)O(V\sum\limits_{i=1}^n \log_2m_i)O(Vi=1∑nlog2mi)。
int new_n=0,new_c[N],new_w[N];
for(int i=1;i<=n;i++){//遍历每种物品
for(int j=1;j<=m[i];j<<=1){//遍历每种物品的个数
new_n++;
m[i]-=j;
new_c[new_n]=j*c[i];
new_w[new_n]=j*w[i];
}
if(m[i]){//若有余数
new_n++;
new_c[new_n]=m[i]*c[i];
new_w[new_n]=m[i]*w[i];
}
}