【洛谷】P8292 [省选联考 2022] 卡牌(根号分治+FMT)

原题链接

题意

n 张卡牌,编号为 1,2,,n。每张卡牌上写着一个正整数,第 i 张卡牌上的正整数为 si

现在有 m 轮游戏,第 i 轮游戏会给出 ci 个质数,需要选择任意多张卡牌,使得这些卡牌上面的正整数的乘积能被该轮游戏给出的每个质数整除。

求每一轮游戏,一共有多少种卡牌的选法,答案对 998244353 取模。

1n1061si20001m15001ci,ici180002pi,j2000

思路

注意到本题中 si 的范围很小,可以考虑从这方面入手。具体地,由于 43×47>2000,所以不存在一个同时有两个 43 的质因子的 si,于是就可以考虑根号分治

首先考虑一下所有 si 都没有 43 以上的质因子的子问题。设 si 中,质因子出现的状态为 Si,即 Si 是一个二进制数,其中第 i 位表示 si 中是否出现过第 i 个质数。这里由于我们只关心每个质因子是否出现,而并不关心它们具体的出现次数,所以每一位的取值只需要表示该质因子是否出现即可。

定义 fi=j=1n[Sji],即所有至少出现了 i 内所有的素数的数。那么 hi=2fi 就表示所选的素数出现情况为 i 的子集的所有方案。不难发现,这里简单容斥一下就可以得出每一种状态的方案数了。然而,这里的 hi 也可以看成是一个高维前缀和的形式,我们只需要对其进行差分(即IFMT)就可以得到每一种状态的方案数。

而对于质因子 43 的情况,可以令 gu,j 表示满足质因子为 u,且所有 <43 的质因子出现状态为 j子集si 的数量。那么对于这些在也出现在给出的质数中的 u,可以在 fj 中先减去 gu,j 这一部分的贡献。由于 u 必须出现,不能拿来容斥,那么单独计算这一部分的贡献就是 2gu,j1。此时 gi=2fi×(2gu,j1),那么此时已经满足了 43 这一部分素数出现的要求,剩下 <43 的部分仍然可以通过 IFMT 计算得到、

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=998244353,N=2023,V=13,M=(1<<V);
int P[V]={2,3,5,7,11,13,17,19,23,29,31,37,41};
int p[N*10],pw[N*N],n,m,cnt[N],f[M],h[M],g[N][M];
void Add(int &a,int b){a+=b,a>=mod&&(a-=mod);}
void Sub(int &a,int b){a-=b,a<0&&(a+=mod);}
void init()
{
	pw[0]=1;for(int i=1;i<=n;i++) pw[i]=(pw[i-1]<<1)%mod;
	for(int i=1;i<N;i++)
	{
		int tmp=i,state=0;if(!cnt[i]) continue;
		for(int j=0;j<V;j++) while(tmp%P[j]==0) tmp/=P[j],state|=1<<j;
		for(int j=0;j<M;j++) if((j&state)==state) f[j]+=cnt[i];//state \subseteq j
		if(tmp==43*43) tmp=43;//43*43<2000
		for(int j=0;j<M;j++) if((j&state)==state) g[tmp][j]+=cnt[i];
	}
}
void OR(int arr[],int op)
{
	for(int i=0;i<V;i++)
	    for(int j=0;j<(1<<V);j++)
	        if((j>>i)&1) Add(arr[j],1ll*op*arr[j-(1<<i)]%mod);
}
void solve()
{
	scanf("%d",&m);memcpy(h,f,sizeof(f));int state=0,ans=0;
	for(int i=1;i<=m;i++) scanf("%d",&p[i]);
	for(int i=1;i<=m;i++) for(int j=0;j<V;j++) if(p[i]==P[j]) state|=1<<j;
	for(int i=1;i<=m;i++) if(p[i]>41) for(int j=0;j<M;j++) h[j]-=g[p[i]][j];
	for(int i=0;i<M;i++)  h[i]=pw[h[i]];
	for(int i=1;i<=m;i++) if(p[i]>41) for(int j=0;j<M;j++) h[j]=1ll*h[j]*(pw[g[p[i]][j]]-1)%mod;
	OR(h,mod-1);
	for(int i=0;i<M;i++) if((i&state)==state) Add(ans,h[i]);
	printf("%d\n",ans);
}
int main()
{
//	freopen("card2.in","r",stdin);
	scanf("%d",&n);for(int x,i=1;i<=n;i++) scanf("%d",&x),cnt[x]++;init();
	int T;scanf("%d",&T);while(T--) solve();
	return 0;
} 
posted @   曙诚  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示