单调队列优化多重背包

多重背包:n个物品,背包承重m,每个物品 重量:wi 价值vi 个数为ci
普通多重背包复杂度:O(nmc)
代码:

for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
		for(int k=1;k<=c[i] && k*w[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*w[i]]+k*v[i]);

单调队列优化的多重背包复杂度:O(nm)
考虑把O(c )的转移压缩为O(1)
变形:
a=j/w[i]
b=j%w[i]
j=b+a
w[i]
思路:承重为j时,不考虑ci限制时最多选a个i号元素。假设a个中不选k个,即选a-k个。
转移方程:

f[i][j]=max(f[i][j],f[i-1][j-(a-k)*w[i]]+(a-k)*v[i]);
等价于
f[i][j]=max(f[i][j],f[i-1][b+k*w[i]]+(a-k)*v[i]);
即是
f[i][j]=max(f[i][j],f[i-1][b+k*w[i]]-k*v[i])+a*v[i];//(a-k<=c[i])

这样max里面就与j无关了,只与b和k有关。
那么我们枚举b和k就行了。然后用单调队列维护max。
b+a*w[i]即为j恰好可以表示1~m的所有数
求f[i][j]用到哪些状态,我来列举下就一目了然了

f[i][b+0*w[i]]=max(f[i-1][b+0*w[i]]-0*v[i])+0*v[i];
f[i][b+1*w[i]]=max(f[i-1][b+0*w[i]]-0*v[i],f[i-1][b+1*[w[i]])-1*v[i])+1*v[i];
f[i][b+2*w[i]]=max(f[i-1][b+0*w[i]]-0*v[i],f[i-1][b+1*[w[i]])-1*v[i],f[i-1][b+2*w[i]]-2*v[i]])+2*v[i];
....
f[i][b+k*w[i]]=max(f[i-1][b+0*w[i]]-0*v[i],f[i-1][b+1*[w[i]])-1*v[i],f[i-1][b+2*w[i]]-2*v[i]]....f[i-1][b+a*w[i]]-k*v[i])+k*v[i];
....
f[i][b+a*w[i]]=max(f[i-1][b+0*w[i]]-0*v[i],f[i-1][b+1*[w[i]])-1*v[i],f[i-1][b+2*w[i]]-2*v[i]]....f[i-1][b+a*w[i]]-a*v[i])+a*v[i];

可以发现f[i][b+k*w[i]]的转移规律,我们就拿单调队列把max里的东西存下来,这样就不用每次枚举选多少个i号物品了
代码片:

for(int b=0; b<w[i]; b++) {
	he=ta=1;
	for(int k=0; k<=(m-b)/w[i]; k++) {//a=(m-b)/w[i],0<=k<=a 
		int j=k*w[i]+b;
		int tmp=f[i-1][b+k*w[i]]-k*v[i];
		while(he<ta && tmp>q[ta-1])//保留当前最优的   
			ta--;
		q[ta]=tmp;
		num[ta++]=k;//不选k个 
		while(he<ta && k-num[he]>c[i]) //选的个数为 k-num[he],且不大于c[i],如果大于了c[i]就删掉 
			  he++;
		f[i][j]=q[he]+k*v[i];
	}
}
		

例题:hdu2191

#include <bits/stdc++.h>
using namespace std;
#define N 110
#define inf 999999999
int t,n,m,w[N],v[N],c[N],ans,he,ta;
int f[N][7005],q[7005],num[7005];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&m,&n);
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++){
            scanf("%d%d%d",&w[i],&v[i],&c[i]);
            for(int b=0;b<w[i];b++){
                he=ta=1;
                for(int k=0;k<=(m-b)/w[i];k++){
                    int j=k*w[i]+b;
                    int tmp=f[i-1][b+k*w[i]]-k*v[i];
                    while(he<ta && tmp>q[ta-1])    ta--;
                    q[ta]=tmp;num[ta++]=k;
                    while(he<ta && k-num[he]>c[i])    he++;
                    f[i][j]=q[he]+k*v[i];
                }
            }
        }
        printf("%d\n",f[n][m]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值