浅谈存在性背包问题

来源:互联网 发布:gps车辆监控软件 编辑:程序博客网 时间:2024/06/02 01:21

Problem

背包是动态规划中的一个十分重要的专题。于是我们就会遇到一些这样的题目,已知n件物品的体积分别为Pi,还有m个背包的容量分别为Ai。每件物品可以取任意多次,询问可以装满多少个背包。
n<=500;m<=300000;P[i]<=100000<=A[i]<=40000000;

Thoughts

这是一个比较基础的DP问题,通常的解法当然是直接套用完全背包问题的模板。其时间复杂度为O(n×maxv)。

for(int i=1;i<=n;i++)  for(int v=maxv;v>=p[i];v--)    f[v]=f[v]||f[v-p[i]];

哇,这题好水啊!
结果再一看数据范围。嗯…逗我呢?数组都要开到爆了!

Solution

题目果然没那么良心
那么应该怎么做呢?

KB says:
而标解的思路是极其巧妙的。我们令物品里最小价格是pmin,对于一个儿子的要求x,如果x模pmin的结果是y,而除了最小价格物品以外的其他物品凑出一个模pmin结果是y的价格z,这个价格比x小,则是x是可以凑出来的(因为x-z的部分可以通过使用pmin来完成)。

好有道理!
真是太强了%%%%

其实意思就是用其他的物品去凑出一个价格z,使得z%pmin=y,又因为x%pmin=y,即有x≡z(mod pmin)。
那么只要满足z<x,那么这之间的差就可以用k个pmin凑出。
那么,我们就可以设置一个数组dis[j],用于表示v%pmin=j时,v的最小值。对于每一个背包容量,只需要判断dis[A[i]%pmin]与A[i]的大小就可以了。
至于dis数组怎么处理呢,这个方法很巧妙——最短路!把每个体积抽象为点,然后分别在当前更新的这个体积的基础上加上其他的物体体积,更新,这样也就可以算出来了。
时间复杂度大概为O(n×pmin)

Code

#include <algorithm>#include <iostream>#include <cstring>#include <cstdio>#include <queue>using namespace std;const int maxn=510,maxm=100010;int n,m,ans,dis[maxm],p[maxn];bool inq[maxm];queue<int> q;void spfa(){    int x,y;    memset(dis,0x3f,sizeof(dis));    dis[0]=0;    q.push(0);    inq[0]=true;    while(!q.empty())    {        x=q.front();        q.pop();        inq[x]=false;        for(int i=2;i<=n;i++)        {            y=(x+p[i])%p[1];            if(dis[y]>dis[x]+p[i])            {                dis[y]=dis[x]+p[i];                if(!inq[y])                  q.push(y),inq[y]=true;            }        }    }}int main(){    int x;    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++)      scanf("%d",&p[i]);    sort(p+1,p+n+1);    spfa();    for(int i=1;i<=m;i++)    {        scanf("%d",&x);        if(dis[x%p[1]]<=x)          ans++;    }    printf("%d\n",ans);    return 0;}