多项式套路入门 (2) - 背包问题
无限背包
这类问题的经典形式:给定一些物品的体积,问用这些物品可以拼出某个范围内的哪些体积。
对于这种问题,考虑构造多项式 \(F_{i}\)。设物品集合为 \(S\),则 \(F_{i}=[i\in S]\),如果物品范围不是从 \(0\) 开始,我们还可以添加一个体积为 \(0\) 的物品。然后构造 \(G=F*F\),则如果 \(G_{i}>0\),那么 \(i\) 可以通过至多 \(2\) 个体积值相加得到。如果 \(G_{i}>0\),使得 \(G_{i}=1\),再构造 \(H=G*G\),则如果 \(H_{i}>0\),那么 \(i\) 可以通过至多 \(4\) 个体积值相加得到。平方 \(q\) 次则得到至多 \(2^{q}\) 个物品体积相加可得的所有体积。时间复杂度 \(O(mlog^2m)\),\(m\) 为值域(即体积范围)。
例题:[CF286E]Ladies' Shop
题目给出两个限制:
\(1\)、选出的数构成的集合可以拼出序列 \(a\) 中的所有数。
2、选出的数构成的集合拼出了一个数 \(x\) 且 \(x\leq m\),则 \(x\) 一定是序列 \(a\) 中的一个数。
根据这两个条件,我们显然得到无解的条件:对于 \(\forall i \in [1,m]\),设 \(S=\{a_{k}\}(1\leq k \leq n)\),则如果 \(i \notin S\) 并且 \(i=a_{p}+a_{q}(1\leq p,q\leq n)\),则无解。因为不满足限制 \((2)\)。
现在考虑有解的情况。设选出的数集为 \(Q\),如果存在 \(Q_{i}=Q_{j}+Q_{k}\),则我们可以考虑不选择 \(Q_{i}\) 而只选择 \(Q_{j}\) 和 \(Q_{k}\),因为 \(Q_{i}\) 可以被拼出,则此时我们可以把 \(Q_{j}+Q_{k}\) 看成是一个数。所以如果存在 \(i\in S\) 并且 \(i=a_{p}+a_{q}(1\leq p,q \leq n)\),则我们不选 \(i\) 这个数。我们的目标转换为求所有满足以上条件的 \(i\)。
考虑设计函数 \(f_{i}\) 表示 \(i\) 能否被拆成序列 \(a\) 中的两数之和。即 \(f_{i}=[\exists j\in [1,i-1] ,j\in S,(i-j)\in S]\),发现 \(a_{i}>0\),则有 \(f_{i}=[\exists j\in [0,i] ,j\in S,(i-j)\in S]\),表示如果 \(f_{i}=1\),则 \(f_{i}\) 能被拆成序列 \(a\) 中的两数之和。发现这个函数非常没用,因为函数之间一点联系都没有......考虑乘法的运算方式,\(1\times 1=1\),\(0\times 1=1\times 0=0\times 0\)。所以我们改 \(f_{i}\) 表示 \(i\) 被拆成序列 \(a\) 中的两数之和的方案数,得到:
观察上面的式子,有点像卷积的形式。我们考虑再构造出一个函数 \(g_{x}=[x\in S]\),则显然的,\(f_{i}=\sum\limits_{j=0}^{i}g_{j}\times g_{i-j}=\sum\limits_{p+q=i}g_{p}\times g_{q}\)
这东西就是卷积形式了。再构造 \(F_{x}=\sum\limits_{j=0}^{M}g_{j}\times x^{i}\),\(G_{x}=\sum\limits_{j=0}^{M}f_{j}\times x^{i}\),则由上式,得到 \(G=F*F\)。
直接 \(\text{FFT}\) 或者 \(\text{NTT}\) 即可解决。最后如果 \(G_{i}=0\) 并且 \(i\in S\),则这个数我们要选中,否则我们不选。
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
//#define int long long
//#define double long double
using namespace std; const int N=4000010, Mod=998244353;
inline int read()
{
int s=0, w=1; ri char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int n,m,a[N],g[N],f[25][2],book[N],T,rev[N];
inline void Get_Rev() { for(ri int i=0;i<T;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?(T>>1):0); }
inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=1ll*x*x%Mod) if(p&1) res=1ll*res*x%Mod; return res; }
inline void NTT(int *s,int type)
{
for(ri int i=0;i<T;i++) if(i<rev[i]) swap(s[i],s[rev[i]]);
for(ri int i=2,cnt=1;i<=T;cnt++, i<<=1)
{
int wn=f[cnt][type];
for(ri int j=0, mid=(i>>1);j<T;j+=i)
{
for(ri int k=0, w=1;k<mid;k++, w=1ll*w*wn%Mod)
{
int x=s[j+k], y=1ll*w*s[j+mid+k]%Mod;
s[j+k]=(x+y)%Mod, s[j+mid+k]=(x-y+Mod)%Mod;
}
}
}
if(!type) for(ri int i=0,inv=ksc(T,Mod-2);i<T;i++) s[i]=1ll*s[i]*inv%Mod;
}
signed main()
{
f[23][1]=ksc(3,119), f[23][0]=ksc(332748118,119);
for(ri int i=22;~i;i--) f[i][1]=1ll*f[i+1][1]*f[i+1][1]%Mod, f[i][0]=1ll*f[i+1][0]*f[i+1][0]%Mod;
n=read(), m=read();
for(ri int i=1;i<=n;i++) a[i]=read(), g[a[i]]=book[a[i]]=1;
T=1; while(T<=m+m) T<<=1; Get_Rev();
NTT(g,1);
for(ri int i=0;i<T;i++) g[i]=1ll*g[i]*g[i]%Mod;
NTT(g,0);
for(ri int i=1;i<=m;i++) if(g[i] && !book[i]) { puts("NO"); return 0; }
puts("YES"); int cnt=0;
for(ri int i=1;i<=n;i++) if(!g[a[i]]) cnt++;
printf("%d\n",cnt);
for(ri int i=1;i<=n;i++) if(!g[a[i]]) printf("%d ",a[i]);
puts("");
return 0;
}