[省选联考 2022] 卡牌
[省选联考 2022] 卡牌
最朴素的暴力就是设 \(f_S\) 为拥有质因数集合 \(S\) 的方案个数。
但是注意到 \(2000\) 以内的质数有将近 \(300\) 个。
考虑根号分治。
注意到每个数只会至多拥有一个大于 \(\sqrt{2000}\) 的质因子。我们发现小于 \(\sqrt{2000}\) 的质数只有 \(14\) 个。
又由于 \(43\times 47> 2000\)。所以不会有一个数同时拥有两个大于等于 \(43\) 且数值不同的质因子。
那么我们就可以重新定义状态。
设 \(f_{i,S}\) 表示拥有质因数集合为 \(\{i\}\cup S\) 的方案个数,其中 \(i\ge43\),且 \(S\) 为前 \(13\) 个质数的子集。
如果 \(i\) 不存在,设其为 \(0\)。
这么做将数分成了若干个块。
每个块内我们单独处理,处理 \(f_{i,S}\) 可以用 dp 来完成,可以理解成 或背包 。
初始值有 \(f_{i,\empty}=1\)。
每添加进一个集合为 \(\{i\}\cup S\) 有转移式:
考虑如何将块内答案合并起来。有:
发现这是一个或卷积的形式,所以我们可以用 FWT 来加速。
设 \(f'\) 表示 \(FWT(f)\)。
易知 \(f'_{\{a\}\cup\{b\},k}=f_{\{a\},k}\times f_{\{b\},k}\)。
那么我们预处理合并 \(g'_{k}=\prod_i f_{\{i\},k}\)。
但是注意到 \(f_{\{i\},k}\) 是包括空集在里面的,而如果 \(i\) 是询问给出的质数,那么空集答案就不能算进去。
设 \(C\) 为给出的质数集合,\(sta\) 为 \(C\) 中小于 \(43\) 的质数构成的集合。
所以我们还得预处理 \(f_{\{i\},k}\) 的逆元。那么最终得到的数组 \(h'\) 就是:
将 \(h'\) 做一次 IDFT 即可得到 \(h\) 。
不难发现答案就是:
复杂度为 \(O(300\times2^{13}\times\log P+\sum C_0\times2^{13} )\)。看起来非常卡,实际跑的飞快!
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD = 998244353;
const int MAXN = 2005;
const int N = (1<<13)+5;
ll qpw(ll x,ll b)
{
ll r=1;
for(;b;b>>=1,x=x*x%MOD) if(b&1)r=r*x%MOD;
return r;
}
bool Small;
int p[MAXN],tot,pr[MAXN],S[MAXN],n,id[MAXN],cnt[MAXN];
int C[MAXN],m;
ll f[305][N],g[N],F[N],inv[305][N];
bool Sunny;
void FWT(ll *f,ll x)
{
for(int l=1;l<(1<<13);l<<=1)for(int i=0;i<(1<<13);i+=l*2)for(int j=i;j<i+l;++j)
f[j+l]=(f[j+l]+f[j]*x+MOD)%MOD;
}
int main()
{
for(int i=2;i<=2000;++i)
{
if(i==43) tot=1;
if(!p[i])
{
id[i]=tot++;
for(int j=i;j<=2000;j+=i)
{
p[j]=1;
if(i<43) S[j]|=(1<<id[i]);
else pr[j]=id[i];
}
}
}
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
int x;scanf("%d",&x);
cnt[x]++;
}
for(int i=0;i<=tot;++i) f[i][0]++;
for(int i=1;i<=2000;++i)
{
ll c=qpw(2,cnt[i])-1;
if(!c) continue;
for(int j=(1<<13)-1;j>=0;--j) f[pr[i]][j|S[i]]=(f[pr[i]][j|S[i]]+c*f[pr[i]][j])%MOD;
}
for(int i=0;i<=tot;++i) FWT(f[i],1);
for(int i=1;i<=tot;++i)
{
for(int j=0;j<(1<<13);++j)
{
inv[i][j]=qpw(f[i][j],MOD-2);
f[0][j]=f[0][j]*f[i][j]%MOD;
}
}
scanf("%d",&m);
while(m--)
{
memset(g,0,sizeof g);
scanf("%d",&C[0]);
for(int i=1;i<=C[0];++i) scanf("%d",&C[i]);
sort(C+1,C+1+C[0]);
int sta=0;
for(int i=1;i<=C[0];++i) if(C[i]<43) sta|=(1<<id[C[i]]);
for(int i=0;i<(1<<13);++i) g[i]=f[0][i];
for(int i=1;i<=C[0];++i)
if(C[i]>=43) for(int j=0,p=id[C[i]];j<(1<<13);++j)
g[j]=(g[j]*inv[p][j]%MOD*(f[p][j]-1+MOD)%MOD)%MOD;
FWT(g,-1);
ll res=0;
for(int i=0;i<(1<<13);++i) if((sta&i)==sta) res=(res+g[i])%MOD;
printf("%lld\n",res);
}
return 0;
}