浅谈存在性背包问题

Problem

背包是动态规划中的一个十分重要的专题。于是我们就会遇到一些这样的题目,已知n件物品的体积分别为Pi,还有m个背包的容量分别为Ai。每件物品可以取任意多次,询问可以装满多少个背包。
n<=500;m<=300000;P[i]<=100000<=A[i]<=40000000; 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 z < x ,那么这之间的差就可以用k个pmin凑出。
那么,我们就可以设置一个数组dis[j],用于表示v%pmin=j时,v的最小值。对于每一个背包容量,只需要判断dis[A[i]%pmin]与A[i]的大小就可以了。
至于dis数组怎么处理呢,这个方法很巧妙——最短路!把每个体积抽象为点,然后分别在当前更新的这个体积的基础上加上其他的物体体积,更新,这样也就可以算出来了。
时间复杂度大概为 O(n×pmin) O ( n × p m i n )

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值