题意:

给定一些物品的价值、大小、数量。求一个大小为 m 的背包最多装得下多少价值的物品。

虽然这道题乱搞都可以 AC,但是我们还是实用单调队列优化

这里的 f[i] 表示容量为 i 的背包可以装下的最大价值 (而不是恰好装下)。因此可以很方便地使用单调队列优化
这所谓的单调队列是神码样的呢?它其实维护了两个单调性:
右边的比左边的新进队。
左边的比右边的优。
为什么这样就对呢?其他单调性为什么不对呢?
这样想:我们至少得维护时间的单调性:也就是右边的比左边的新进队。为什么同时左边的要比右边的优呢?感性地想:在队列中的元素向左侧 “流动” 时,我们会 “失去” 非常优秀的解而只能从新进来的解中转移。因此单调性是左边优于右边。
有了单调队列的思路,有了状态转移方程,我们就可以轻松的写出代码。
噢,不知道大家有没有想到这样一个问题:
由于状态转移方程是:
f[i]=max(f[i-kw[i]]+kv[i])
我们不能直接将每个 f[i] 的值存进队列,而应该保存 f[i]-i/w[i]*v[i](仔细想一想为什么)
有了以上这些准备工作,我们的程序差不多就完备了。

#include <iostream>
#include <cstdio>
#include <cstring>

#define max(a,b) ((a>b)?(a):(b))
#define MX 101

using namespace std;

int v[MX],w[MX],c[MX];
int n,m;

void input()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&w[i],&v[i],&c[i]);
}

pair<int,int>q[MX];
int h,t;
int f[MX];

void work()
{
    int relex;
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        if(c[i]==0)continue;
        if(c[i]>m/w[i])c[i]=m/w[i];
        for(int j=0;j<w[i];j++)
        {
            h=0,t=1;
            for(int k=0;k<=(m-j)/w[i];k++)             //这里我们直接枚举装入几个物品,而不是枚举装到多大空间。
            {
                relex=f[k*w[i]+j]-k*v[i];                      //详见上文的分析
                while(q[h].first<relex&&h>=t)h--;
                q[++h].first=relex;
                q[h].second=k;
                while(q[t].second<k-c[i])t++;
                f[k*w[i]+j]=q[t].first+k*v[i];
            }
        }
    }
    printf("%d\n",f[m]);
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int i=1;i<=t;i++)
    {
        input();
        work();
    }
    return 0;
}
分类: 文章

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注