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;
}