题目:https://www.acwing.com/problem/content/4/
多重背包题意同完全背包,不过多了一个限制,就是每种物品有数目限制。
朴素想法
最朴素的想法就是直接枚举每种物品拿多少个。
1 #include<iostream> 2 using namespace std; 3 const int N=110; 4 int v[N],w[N],s[N]; 5 int f[N][N]; 6 int main(void){ 7 int n,m; 8 cin>>n>>m; 9 for(int i=1;i<=n;i++){ 10 cin>>v[i]>>w[i]>>s[i]; 11 } 12 for(int i=1;i<=n;i++){ 13 for(int j=0;j<=m;j++){ 14 f[i][j]=f[i-1][j]; 15 for(int k=1;k<=s[i]&&j>=k*v[i];k++){ 16 f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]); 17 } 18 } 19 } 20 cout<<f[n][m]; 21 return 0; 22 }
但是这样做的时间复杂度为n*m*s,时间代价过高。
二进制优化
我们如果可以用更少的数将 s [ i ] 代替的话,同时保持答案的准确性的话,时间就会减少。
需要用到的定理
用来表示s [ i ] 的数的获取方法
那么在将s [ i ] 替换之后,只需要把新的物品序列当成01背包来做就OK了。
1 #include<iostream> 2 using namespace std; 3 const int N=12000,M=2100; 4 int v[N],w[N],cnt; 5 int f[M]; 6 int main(void){ 7 int n,m; 8 cin>>n>>m; 9 for(int i=0;i<n;i++){ 10 int a,b,s; 11 cin>>a>>b>>s; 12 int k=1; 13 while(k<=s){ 14 cnt++; 15 v[cnt]=a*k; 16 w[cnt]=b*k; 17 s-=k; 18 k<<=1; 19 } 20 if(s){ 21 cnt++; 22 v[cnt]=a*s; 23 w[cnt]=b*s; 24 } 25 } 26 for(int i=1;i<=cnt;i++){ 27 for(int j=m;j>=v[i];j--){ 28 f[j]=max(f[j],f[j-v[i]]+w[i]); 29 } 30 } 31 cout<<f[m]; 32 return 0; 33 }
单调队列优化
(整不明白)
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=20010; 6 int n,m; 7 int f[N],g[N],q[N]; 8 int main(void){ 9 cin>>n>>m; 10 for(int i=0;i<n;i++){ 11 int c,w,s; 12 cin>>c>>w>>s; 13 memcpy(g,f,sizeof(f)); 14 15 for(int j=0;j<c;j++){ 16 int hh=0,tt=-1; 17 18 for(int k=j;k<=m;k+=c){ 19 // f[k]=g[k]; 20 while(hh<=tt && k-s*c > q[hh]) hh++;//q[hh] < k- s* c代表全部放进去还没有放满,而w恒大于0,所以其他的策略比更优 21 if(hh<=tt) f[k]=max(f[k],g[q[hh]] + (k-q[hh]) / c *w ); 22 while(hh<=tt&&g[q[tt]] - (q[tt]-j) / c * w <= g[k]-(k-j) / c * w) tt--; 23 q[++tt]=k; 24 } 25 } 26 } 27 cout<<f[m]<<endl; 28 return 0; 29 }