题目:
https://www.acwing.com/problem/content/6/
有 N 种物品和一个容量是 V 的背包。
第 ii 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V (0<N≤1000(0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V≤200000
0<vi,wi,si≤200000
提示
本题考查多重背包的单调队列优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
发现每种物品数量足够多直接做完全背包肯定会超时,在此介绍两种优化的方法。
二进制优化:
我们可以把某些物品看成一个新的物品,就比如有10个重量为1,价值为2的物品,那么我们可以把它们拆成重量为1,价值为2;重量为2,价值为4;重量为4,价值为8;重量为3,价值为6四种物品,然后做0-1背包就行了,这样也可以枚举取0-10个的所有情况。
这样时间复杂度就成n*m*log(w)了。值得注意的是:当某个数量w与价值val的积大于等于背包总容量m(即w*val>=m)时可以看成完全背包。
单调队列:时间复杂度为n*m
这个还是推荐一篇博客供参考吧(QAQ):https://www.cnblogs.com/-guz/p/9866118.html
#include<bits/stdc++.h> using namespace std; int dp[20005]; struct st{ int val,pos; }que[20005]; int tail; int head; int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=0;i<n;i++){ int w,val,num; scanf("%d%d%d",&w,&val,&num); int cnt=min(num,m/w); for(int j=0;j<w;j++){//j为余数 tail=0; head=1; for(int k=0;k<=(m-j)/w;k++){ while(head<=tail&&que[head].pos<k-cnt)head++; while(head<=tail&&que[tail].val<=dp[j+k*w]-k*val)tail--; que[++tail].val=dp[j+k*w]-k*val; que[tail].pos=k; // tail++; dp[j+k*w]=que[head].val+k*val; } } } // for(int i=0;i<=m;i++) printf("%d",dp[m]); return 0; }
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; typedef long long ll; ll dp[20005]; int main(){ int n,m; int w,v,num; scanf("%d%d",&n,&m); while(n--){ scanf("%d%d%d",&w,&v,&num); if(w*num>=m){ for(int i=w;i<=m;i++){ dp[i]=max(dp[i],dp[i-w]+v); } } else{ int w1,v1; int cnt=1; while(num>=cnt){ num-=cnt; w1=cnt*w; v1=cnt*v; for(int i=m;i>=w1;i--){ dp[i]=max(dp[i],dp[i-w1]+v1); } cnt*=2; } if(num>0){ w1=num*w; v1=num*v; for(int i=m;i>=w1;i--){ dp[i]=max(dp[i],dp[i-w1]+v1); } } } } printf("%lld ",dp[m]); return 0; }
(第二种竟然比一种还快。。。。)