[DP]多重背包
多重背包
问题描述:给定 n n n种物品和一个体积为 V V V的背包,第 i i i种物品数量为 m i m_i mi,体积为 c i c_i ci,价值为 w i w_i wi。如何装填背包使总价值最大?
通过直接求解,转移方程式: d p [ i ] [ j ] = max ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − k × c [ i ] ] + k × w [ i ] ) , k ∈ [ 1 , min ( m [ i ] , j c [ 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 = 1 n m i ) O(V\sum\limits_{i=1}^n m_i) O(Vi=1∑nmi),超时。
实际上,多重背包属于 0 / 1 0/1 0/1背包的推广,易得其可转换为 0 / 1 0/1 0/1背包问题:将第 i i i种物品视为 m i m_i mi种独立(不同)的物品,并按 0 / 1 0/1 0/1背包求解。定义状态数组 d p [ i ] [ j ] dp[i][j] dp[i][j],表示将前 i i i个物品放入容积为 j j j的背包时的最大价值。实际上复杂度不变,仍为 O ( V ∑ i = 1 n m i ) 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的幂次经过有限次相加得到,以 2 i ( i ∈ [ 0 , ⌈ log 2 m i ⌉ + 1 ] ) 2^i(i\in[0,\lceil\log_2m_i\rceil+1]) 2i(i∈[0,⌈log2mi⌉+1])顺次拆分,最后可能有一个余数。因此使用倍增即可将第 i i i种物品变为 log 2 m i \log_2m_i log2mi个,每个物品体积为 2 k × c i 2^k\times c_i 2k×ci,价值为 2 k × w i 2^k\times w_i 2k×wi。
以下为二进制拆分代码,之后使用new_n
、new_c
、new_w
以
0
/
1
0/1
0/1背包求解即可。复杂度
O
(
V
∑
i
=
1
n
log
2
m
i
)
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];
}
}