多重背包优化
多重背包优化
二进制拆分
18个物品可以拆成1,2,4,8,3倍物品,这样5个物品任意取可以得到1~18个物品的所有方案。
时间复杂度是\(O(nWlogP)\),P是一种物品的最大数量
#include <bits/stdc++.h>
using namespace std;
const int maxw=4e4+5;
const int maxn=1e4+5;
int dp[2][maxw];
int v[maxn],w[maxn];
int main () {
int n,W;
scanf("%d%d",&n,&W);
int cnt=0;
for(int i=1;i<=n;i++){
int v1,w1,m1;
scanf("%d%d%d",&v1,&w1,&m1);
int j=1;
while((1<<j)-1<m1){
v[++cnt]=v1*(1<<(j-1));
w[cnt]=w1*(1<<(j-1));
j++;
}
j--;
int rest=m1-((1<<j)-1);
v[++cnt]=rest*v1;
w[cnt]=rest*w1;
}
for(int i=1;i<=cnt;i++){
int cur=i&1;
for(int j=0;j<=W;j++)dp[cur][j]=dp[cur^1][j];
for(int j=w[i];j<=W;j++){
dp[cur][j]=max(dp[cur][j],dp[cur^1][j-w[i]]+v[i]);
}
}
int ans=0;
for(int j=0;j<=W;j++)ans=max(ans,dp[cnt&1][j]);
printf("%d\n",ans);
}
单调队列
w:重量,v:价值,可以得到一般转移方程
\[f[j]=max(f[j−w∗k]+v∗k);(k<=c)
\]
设\(d=j\bmod w\),\(s=j/w\)
\[f[j]=max(f[d+w∗k]−v∗k)+v∗s(s-k<=c)
\]
可以发现对j按模w的余数划分剩余类,dp的转移只会发生在每一个剩余类之内。因此可以枚举模数d,用单调队列维护一个大小为k的窗口的\(f[d+w∗k]−v∗k\)的最大值,这样每次转移都是O(1)的,总的背包的时间复杂度是O(n*W)
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e2+5;
const int maxw=4e4+5;
int dp[maxw];
int v[maxn],w[maxn],m[maxn];
int main () {
int n,W;
scanf("%d%d",&n,&W);
int ans=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&v[i],&w[i],&m[i]);
if(w[i]==0)ans+=v[i]*m[i];
else m[i]=min(m[i],W/w[i]);
}
for(int i=1;i<=n;i++){
if(w[i]==0)continue;
for(int d=0;d<w[i];d++){//枚举余数
int k=(W-d)/w[i];//状态数
vector<int>qi(k+2),qv(k+2);//idx,value
int l=1,r=0;
for(int j=0;j<=k;j++){
while(r>=l&&dp[d+j*w[i]]-j*v[i]>=qv[r])r--;
qv[++r]=dp[d+j*w[i]]-j*v[i];
qi[r]=j;
while(r>=l&&qi[l]<j-m[i])l++;
dp[d+j*w[i]]=max(dp[d+j*w[i]],qv[l]+j*v[i]);
}
}
}
int maxx=0;
for(int i=0;i<=W;i++)maxx=max(maxx,dp[i]);
ans+=maxx;
printf("%d\n",ans);
}