DP--多重背包--队列优化

问题描述:给出背包大小,给出一些物品,每个物品有一个重量、价值、个数,求能装进背包的最大总价值。
我们知道,这样的问题有很多种解法,比如可以用二进制拆分来使每个物品的个数变成原来的log,但是,这样显然还不够,下面我们来讨论利用队列来将多重背包优化到O(n*allv)的复杂度。

我们知道,背包问题的转移方程为 f[i][j]=max(f[i1][j],f[i1][jw[i]]+p[i])

如果将背包大小按模当前物品的重量分类,可以发现, f[i][j] 可由与它模当前物品的重量相同的背包大小转移得,并且当前背包大小与前一状态相差的物品数必须小于等于当前物品的个数。这样,就把问题转化成了求以当前背包大小为末端点的某一段固定长度的区间中的最优解,这就是经典的滑动窗口问题,可以用队列优化到线性的复杂度,简单来说,以求区间最大值为例,就是用队列维护一个单调降的队列,每次的最优解就是队列头,每次有新的节点加入时从队尾开始,把所有不优于它的节点删除。

那么如何判断两个状态那个更优?假设i状态优于j,now为当前的背包大小。

f[i]+(nowi)/wp>f[j]+(nowj)/wp
f[i]i/wp>f[j]j/wp

所以我们得到,f[i]-i/w*p越大的状态越优。

最后来估计复杂度:每个物品放一次,每个物品的每个剩余系放一次,每个剩余系放allv/w次,所以复杂度为 O(nallv/ww)=O(nallv)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 2006
#define maxv 506
using namespace std;
struct data{
    int w,p,m;
}a[maxn];
int n,allv,hed,tal,f[maxn][maxv],que[maxv];
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
int main(){
    freopen("hallows.in","r",stdin);
    freopen("hallows.out","w",stdout);
    n=_read();allv=_read();
    for(int i=1;i<=n;i++)a[i].m=_read(),a[i].w=_read(),a[i].p=_read();
    for(int i=1;i<=n;i++)
     for(int s=0;s<a[i].w;s++){
        que[hed=tal=1]=s;f[i][s]=f[i-1][s];
        for(int j=s+a[i].w;j<=allv;j+=a[i].w){
            while(hed<=tal&&(((j-que[hed])/a[i].w)>a[i].m))hed++;
            f[i][j]=max(f[i-1][j],f[i-1][que[hed]]+((j-que[hed])/a[i].w)*a[i].p);
            while(hed<=tal&&(f[i-1][j]-j/a[i].w*a[i].p>=f[i-1][que[tal]]-que[tal]/a[i].w*a[i].p))tal--;
            que[++tal]=j;
        }
     }
    printf("%d",f[n][allv]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值