HDU 1171 Big Event in HDU (单调队列优化多重背包)

来源:互联网 发布:哪个洗衣店好 知乎 编辑:程序博客网 时间:2024/06/05 15:19

题目描述

传送门

题目大意:共n个物品,每个物品有一个大小和数量,将所有的物品分成两部分,是两部分的大小尽量平均

题解

看成是多重背包,背包的体积是sum/2,求能装下物品的最大大小和。

然后就是用单调队列优化的问题了。

正常的转移:f[i][j]=max(f[i][jkv[i]]+kw[i],f[i][j])  0<=k<=c[i]这样的之间复杂度是O(Vc[i])
比较常见的优化是二进制优化,将c[i]分成0,1,2,4,....,2k,然后再做01背包,时间复杂度是O(Vlogc[i])
其实还能继续优化,用单调队列可以优化到O(Vn)
a=j/v[i],b=j%v[i],那么j就能表示成j=av[i]+b,用k表示取第i件物品的件数比a少k件。
那么转移可以表示成
f[i][j]=max(f[i][j],(f[i1][b+kv[i]]kw[i])+aw[i]
那么枚举i,j后真正的变量就只有k了,用单调队列维护f[i1][b+kv[i]]kw[i]在合法范围内的最大值即可。

比较具体的讲解

代码

#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#include<iostream>#define N 1005#define M 250003using namespace std;int n,m,A,B,head,tail,head1,tail1;int v[N],c[N],q[M],q1[M],f[M];int main(){    freopen("a.in","r",stdin);    while (true) {        scanf("%d",&n);        if (n<0) break;        m=0;        for(int i=1;i<=n;i++) {            scanf("%d%d",&v[i],&c[i]);            m+=v[i]*c[i];        }        for (int i=0;i<=m;i++) f[i]=0;        int sum=m;        m=m>>1;        for (int i=1;i<=n;i++)          for (int j=0;j<v[i];j++) {            head=tail=0;            head1=tail1=0;            for (int k=j,cnt=0;k<=m;k+=v[i],cnt++) {                if (tail-head==c[i]+1) {                    if (q1[head1+1]==q[head+1]) head1++;                    head++;                 }                int t=f[k]-cnt*v[i];                q[++tail]=t;                while (head1<tail1&&q1[tail1]<t) --tail1;                q1[++tail1]=t;                f[k]=q1[head1+1]+cnt*v[i];            }         }         B=f[m];         A=sum-B;         printf("%d %d\n",A,B);    }}