[省选联考 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\) 有转移式:

\[f_{i,k}=\sum_{j|S=k} f_{i,j}\times cnt_{i,S} \]

考虑如何将块内答案合并起来。有:

\[f_{\{a\}\cup \{b\},k}=\sum_{i|j=k} f_{\{a\},i}\times f_{\{b\},j} \]

发现这是一个或卷积的形式,所以我们可以用 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'=g'_{k}\prod_{i\in C,i>43} {\dfrac{f_{\{i\},k}-1}{f_{\{i\},k}}} \]

\(h'\) 做一次 IDFT 即可得到 \(h\)

不难发现答案就是:

\[\sum_{sta\in k}h_k \]

复杂度为 \(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;
}
posted @ 2022-04-21 16:37  夜空之星  阅读(68)  评论(0编辑  收藏  举报