题目大意
有
n
种物品,物品的体积分别为
1≤n≤50,1≤m≤100000,Wi≤1018
题目分析
先考虑普通的暴力怎么做。
令
fi,j
表示使用了
i
个大体积物品,是否能耗容量
然后我们发现如果
Vi
中的最小值都大于等于
L
,那么显然背包总容量不超过
但是如果
Vi
中的最小值小于
L
呢?这里用到了一个十分巧妙的方法。在
假设
V1
是所有
V
中最小的。我们先考虑放了多少个大物品,设
那么这个东西如何转移呢?在
fi
转移到
fi+1
之前,我们还要处理在
fi
中塞各种小物品的情况。令
gk,j
表示在
fi
的基础上塞了一些小物品,目前考虑到第
k
个小物品,容量和对
虽然这里写的是dp套dp,实际实现可以把
f
数组和
时间复杂度
O(nmV)
。
代码实现
由于原题没有 Vmin≥L 的测试点,所以那一部分的暴力我就偷懒没有打了。
#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;
}