[JZOJ4916]完全背包问题

来源:互联网 发布:看电视台的软件 编辑:程序博客网 时间:2024/05/07 05:00

题目大意

n种物品,物品的体积分别为V1,V2,...,Vn,且每种物品的数量都可以看做是无限多的。现在有m次询问,每次询问给定一个容量为Wi的背包,请你回答是否存在一种物品选择方案,使得背包恰好能被完全装满。同时要求,所有选出的物品中,体积不小于L的物品总数量不能超过C件。

1n50,1m100000,Wi1018


题目分析

先考虑普通的暴力怎么做。
fi,j表示使用了i个大体积物品,是否能耗容量j。直接dp转移。
然后我们发现如果Vi中的最小值都大于等于L,那么显然背包总容量不超过CV,然后直接dp是不会T的。
但是如果Vi中的最小值小于L呢?这里用到了一个十分巧妙的方法。在Wi很大的时候,其实很多物品直接的组合可以分解成较少物品的组合加上一堆最小的物品。
假设V1是所有V中最小的。我们先考虑放了多少个大物品,设fi,j表示使用了i个大物品,凑出来的容量之和对V1取模得到j的方案中容量之和最小是多少。如果知道了这个,处理询问时我们只需要看看所有fi,Wi mod V1中是否有值小于等于Wi的即可,因为如果存在,那么其必然能够通过加上一堆V1来凑出。
那么这个东西如何转移呢?在fi转移到fi+1之前,我们还要处理在fi中塞各种小物品的情况。令gk,j表示在fi的基础上塞了一些小物品,目前考虑到第k个小物品,容量和对V1取模得到j的所有方案中最小值。那么我们可以从gk,j转移到gk,(j+Vk)mod V1,注意这样就形成了若干个环。显然这个环上的最小值是不可能被更新的,因此我们直接从一个环上最小值所在位置向后递推即可。得到了gk,j后,我们便处理出了fi的最终值,这个时候我们便可以计算出fi+1的初始值了,直接枚举新加入的大物品是哪一个即可。
虽然这里写的是dp套dp,实际实现可以把f数组和g数组合并成一个三维数组。
时间复杂度O(nmV)


代码实现

由于原题没有VminL的测试点,所以那一部分的暴力我就偷懒没有打了。

#include <algorithm>#include <iostream>#include <climits>#include <cstdio>using namespace std;typedef long long LL;const LL INF=LLONG_MAX/2;const int N=55;const int V=10005;const int C=35;int v[N],p[N];LL f[C][N][V];//Large bag/Item ID/sum(V)%a[1]bool vis[V];int n,m,l,c,n0;void dp(int c0){    n0=0;    for (int i=1;i<=n;i++)    {        if (v[i]>=l) break;        n0=i;        for (int j=0;j<v[1];j++) vis[j]=0;        for (int j=0,mi;j<v[1];j++)        {            if (vis[j]) continue;            p[0]=0,mi=-1;            for (int x=j;!vis[x];(x+=v[i])%=v[1])            {                vis[x]=1,p[++p[0]]=x;                if (mi==-1||f[c0][i-1][x]<f[c0][i-1][mi]) mi=x;            }            f[c0][i][mi]=f[c0][i-1][mi];            for (int x=(mi+v[i])%v[1];x!=mi;(x+=v[i])%=v[1]) f[c0][i][x]=min(f[c0][i-1][x],f[c0][i][((x-v[i])%v[1]+v[1])%v[1]]+v[i]);        }    }}int main(){    freopen("bag.in","r",stdin),freopen("bag.out","w",stdout);    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++) scanf("%d",&v[i]);    scanf("%d%d",&l,&c);    sort(v+1,v+1+n);    f[0][0][0]=0;    for (int i=1;i<v[1];i++) f[0][0][i]=INF;    dp(0);    for (int i=1;i<=c;i++)    {        for (int j=0;j<v[1];j++) f[i][0][j]=INF;        for (int j=n;j>=1;j--)        {            if (v[j]<l) break;            for (int k=0;k<v[1];k++) f[i][0][k]=min(f[i][0][k],f[i-1][n0][((k-v[j])%v[1]+v[1])%v[1]]+v[j]);        }        dp(i);    }    for (int i=1;i<=m;i++)    {        LL w;        bool ok=0;        scanf("%lld",&w);        for (int j=0;j<=c;j++)            if (f[j][n0][w%v[1]]<=w)            {                ok=1;                break;            }        printf(ok?"Yes\n":"No\n");    }    fclose(stdin),fclose(stdout);    return 0;}
0 0