任务
有N种物品,每种物品的数量为C1,C2……Cn。从中任选若干件放在容量为W的背包里,每种物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。
第1行,2个整数,N和W中间用空格隔开。N为物品的种类,W为背包的容量。(1 <= N <= 100,1 <= W <= 50000)
第2 - N + 1行,每行3个整数,Wi,Pi和Ci分别是物品体积、价值和数量。(1 <= Wi, Pi <= 10000, 1 <= Ci <= 200)
解法
裸的多重背包跑
O(n∗w∗∑ci)
;
显然这是不被接受的。
徐持衡的论文中《浅谈几类背包题》有
O(n∗m∗log)
和
O(n∗m)
的做法;
这里只介绍
O(n∗m)
的做法:
设
f[i]
表示使用了
i
体积,的最大价值。
我们有,
我们注意到,在这个方程中, f[j] 实际上是被 f[j−1∗wi],f[j−2∗wi],...,f[j−ai∗wi] 所更新的。
这些 j−1∗wi,j−2∗wi,...,j−ai∗wi 在 mod wi 意义下,余数相同的。
所以我们考虑先枚举余数 d ,然后再枚举+了多少个
显然对于不同余数的转移,都是互相独立的。
然后我们观察转移方程,其实与单调队列很相像。
注意到体积的不同,所以我们在加入单调队列的时候,加入的是
f[d+j∗wi]−j∗vi
。
时间复杂度为
O(n∗m)
代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#include<string.h>
using namespace std;
const char* fin="beibao.in";
const char* fout="beibao.out";
const int inf=0x7fffffff;
const int maxn=107,maxm=500007;
int n,m,i,j,k,ans;
int f[maxm],a[maxn],w[maxn],v[maxn];
int b[maxm][2],head,tail;
void add(int xx,int yy){
while (head<=tail && b[tail][1]<=yy) tail--;
b[++tail][0]=xx;
b[tail][1]=yy;
}
int main(){
freopen(fin,"r",stdin);
freopen(fout,"w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++) scanf("%d%d%d",&w[i],&v[i],&a[i]);
for (i=1;i<=n;i++){
for (j=0;j<w[i];j++){
head=1;
tail=0;
for (k=0;k<=(m-j)/w[i];k++){
add(k,f[j+k*w[i]]-k*v[i]);
if (k-b[head][0]>a[i]) head++;
f[j+k*w[i]]=b[head][1]+k*v[i];
ans=max(ans,f[j+k*w[i]]);
}
}
}
printf("%d",ans);
return 0;
}