有
n
n
n 种物品,第 i 种物品有
c
i
ci
ci 个,体积为
v
i
vi
vi。
给定整数集合
S
S
S,从这 n 种物品中选出
k
(
k
∈
S
)
k(k ∈ S )
k(k∈S)种物品,每种物品选任意正整数
个,问可以得到哪些小于
L
L
L 的总体积值。
所有输入数据小于等于2000
首先 f [ i ] [ j ] f[i][j] f[i][j]表示选了 j j j种物品,能否获得 i i i的体积,然后可以 b i t s e t bitset bitset优化到 O ( n 4 / w ) O(n^4/w) O(n4/w)
然后考虑这个在转移中,假设当前枚举到第
i
i
i种物品,对于一个
f
[
j
]
f[j]
f[j],能够转移来的
f
[
k
]
f[k]
f[k]间相隔的距离是相同的,是
v
[
i
]
v[i]
v[i],所以可以把之前的可以转移到的体积根据对
v
[
i
]
v[i]
v[i]取模的值分类,然后转移就是当前这个
f
[
i
]
f[i]
f[i]或上之前的
f
[
i
−
1
]
<
<
1
,
f
[
i
−
2
]
<
<
1
,
.
.
.
,
f
[
i
−
c
]
<
<
1
f[i-1]<<1,f[i-2]<<1,...,f[i-c]<<1
f[i−1]<<1,f[i−2]<<1,...,f[i−c]<<1,然后此时复杂度还是很高,但是仔细观察可以发现,如果将
f
[
i
]
f[i]
f[i]每
c
c
c个预处理出来,就可以把每个转移看成一个前缀和一个后缀或起来.
这样时间复杂度
O
(
n
3
/
w
)
O(n^3/w)
O(n3/w)
#include<bits/stdc++.h>
using namespace std;
inline int read(){
char c=getchar();int t=0,f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
return t*f;
}
const int maxn=2005;
bitset<maxn> dp[maxn],pre[maxn],suf[maxn],ans;
int n,l,m,cnt,c,v;
bitset<maxn> get(int l,int r){//这里就是dp转移
if(l%c==0)return pre[r];
else return suf[l]|pre[r];
}
int main(){
n=read(),l=read();dp[0][0]=1;
for(int i=1;i<=n;i++){
c=read(),v=read();
for(int j=0;j<v;j++){
for(int k=0;k*v+j<=l;k++){pre[k]=suf[k]=dp[k*v+j]<<1;cnt=k;}
for(int k=cnt-1;k>=0;k--)if((k+1)%c)suf[k]|=suf[k+1];
for(int k=1;k<=cnt;k++)if(k%c)pre[k]|=pre[k-1];
for(int k=1;k<=cnt;k++)dp[k*v+j]|=get(max(0,k-c),k-1);
}
}
m=read();
for(int i=1;i<=m;i++){
int x=read();ans[x]=1;
}
for(int i=1;i<=l;i++)if((dp[i]&ans).count())printf("%d ",i);
return 0;
}