[SNOI2017]遗失的答案

题目描述了一位用户忘记了购买的皮肤编号,但知道这些皮肤编号的最大公约数G和最小公倍数L。问题转换为求解在1到n/G范围内,有多少种合法的皮肤编号组合,其组合的最大公约数为1且最小公倍数为L/G,其中x/G是询问的皮肤编号。解决方案利用快速沃尔什变换和动态规划,计算满足条件的皮肤编号组合数量,并对查询进行O(1)时间复杂度的回答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷P5366 [SNOI2017]遗失的答案

题目大意

n n n个皮肤,编号为 1 1 1 n n n。小皮球买了若干个皮肤,这些皮肤的编号的最大公约数为 G G G,最小公倍数为 L L L

但是,他忘了自己买了哪些皮肤。现在,有 q q q组询问,每组询问有一个数字 x x x,求有多少种合法的方案中,购买了皮肤 x x x

对于每组询问,输出答案模 1 0 9 + 7 10^9+7 109+7后的值。

1 ≤ n , G , L ≤ 1 0 8 1\leq n,G,L\leq 10^8 1n,G,L108 1 ≤ q ≤ 1 0 5 1\leq q\leq 10^5 1q105 1 ≤ x ≤ 1 0 8 1\leq x\leq 10^8 1x108


题解

前置知识:快速沃尔什变换( F W T FWT FWT

转化题意

题意可转化为:求在 1 1 1 ⌊ n G ⌋ \lfloor\dfrac{n}{G}\rfloor Gn中选择最大公约数为 1 1 1、最小公倍数为 L G \dfrac{L}{G} GL的合法方案中,购买的皮肤 x G \dfrac{x}{G} Gx的方案的数量。

当然,如果 L ∤ G L\nmid G LG或者 x ∤ G x\nmid G xG,则方案数为 0 0 0

下面, L = L G , x = x G L=\dfrac{L}{G},x=\dfrac{x}{G} L=GL,x=Gx

状压 D P DP DP

因为 L ≤ 1 0 8 L\leq 10^8 L108,分解质因子后,质因子的数量不超过 8 8 8个。考虑状压 D P DP DP

若要保证最大公约数为 1 1 1,则对于每一个 L L L的质因子,都应存在至少一个皮肤,使得这个皮肤的编号分解质因数后这个质因子的指数为 0 0 0

若要保证最小公倍数为 L L L,则对于每一个 L L L的质因子,都应存在至少一个皮肤,使得这个皮肤的编号分解质因数后这个质因子的指数与 L L L的相同。

L L L的质因子个数为 k k k,则状压的长度为 2 k 2k 2k。前 k k k个位置表示是否有数的这个质因子的指数为 0 0 0,后 k k k个位置表示是否有数的这个质因子的指数达到上界。这个状态表示的对象是皮肤的编号,而需要用到的皮肤编号只有 L L L的因子。

看似最多有 2 2 k 2^{2k} 22k种状态,但如果质因子的指数为 1 1 1,则一个数要么为 0 0 0,要么达到上界。而如果不是 1 1 1,那 k k k就不再能取到 8 8 8。指数不是 1 1 1的质因子个数越多, k k k就越小。经过计算,最终的总状态数在 650 650 650之内。

f i , j f_{i,j} fi,j表示前 i i i个数选若干个数后状态为 j j j的方案数, g i , j g_{i,j} gi,j表示第 i i i个数及之后的数中选若干个数后状态为 j j j的方案数。

设第 i i i个数的状态为 s t st st,则 x x x为第 i i i个数时的答案为

a n s [ i ] = ∑ S ∣ s t = 2 2 k − 1 ∑ x ∣ y = S f i − 1 , x × g i + 1 , y ans[i]=\sum\limits_{S|st=2^{2k}-1}\sum\limits_{x|y=S}f_{i-1,x}\times g_{i+1,y} ans[i]=Sst=22k1xy=Sfi1,x×gi+1,y

F W T FWT FWT

那怎么求上面这个式子呢?用 F W T FWT FWT即可。

那么,因为 x x x一定是 G G G的因数,所以在 a n s ans ans数组中一定能找到对应的答案。那么查询就是 O ( 1 ) O(1) O(1)的了。

时间复杂度为 O ( L + v × 2 2 k × 2 k + q log ⁡ v ) O(\sqrt L+v\times 2^{2k}\times 2k+q\log v) O(L +v×22k×2k+qlogv)。其中 v v v表示状态数, k k k表示 L L L的质因子个数。 v v v的最大值为 650 650 650 k k k的最大值为 8 8 8。因为 v v v k k k不能同时取到最大值,而且跑不满,时限有 2 s 2s 2s,所以还是可以过的。

code

#include<bits/stdc++.h>
using namespace std;
int n,G,L,qt,all,cnt=0,p[15],q[15],v[655],num[100005],s[1<<16];
int w[655],ans[655],f[655][1<<16],g[655][1<<16],h[655][1<<16];
const int mod=1000000007;
void pt(int x){
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
			p[++p[0]]=i;
			while(x%i==0){
				x/=i;++q[p[0]];
			}
		}
	}
	if(x>1){
		p[++p[0]]=x;q[p[0]]=1;
	}
}
int gt(int x){
	int re=0;
	for(int i=1;i<=p[0];i++){
		int vt=0;
		while(x%p[i]==0){
			x/=p[i];++vt;
		}
		if(vt==0) re|=(1<<i-1);
		if(vt==q[i]) re|=(1<<i-1+p[0]);
	}
	return re;
}
int mi(int t,int v){
	if(!v) return 1;
	int re=mi(t,v/2);
	re=1ll*re*re%mod;
	if(v&1) re=1ll*re*t%mod;
	return re;
}
void fwt_or(int *e,int fl){
	for(int s=2;s<=all;s<<=1){
		int mid=s>>1;
		for(int bl=0;bl<all;bl+=s){
			for(int i=0;i<mid;i++){
				e[bl+mid+i]=(e[bl+mid+i]+1ll*fl*e[bl+i]+mod)%mod;
			}
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&G,&L);
	if(L%G){
		int x;
		scanf("%d",&qt);
		while(qt--){
			scanf("%d",&x);
			printf("0\n");
		}
		return 0;
	}
	L/=G;n/=G;pt(L);
	for(int i=1;i*i<=L&&i<=n;i++){
		if(L%i==0){
			num[++num[0]]=i;
			if(L/i<=n&&L/i!=i) num[++num[0]]=L/i;
		}
	}
	for(int i=1;i<=num[0];i++){
		++s[gt(num[i])];
	}
	all=1<<(p[0]*2);
	for(int i=0;i<all;i++){
		if(s[i]){
			v[++cnt]=i;w[cnt]=mi(2,s[i])-1;
		}
	}
	f[0][0]=g[cnt+1][0]=1;
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<all;j++){
			f[i][j]=(f[i][j]+f[i-1][j])%mod;
			f[i][j|v[i]]=(f[i][j|v[i]]+1ll*f[i-1][j]*w[i]%mod)%mod;
		}
	}
	for(int i=cnt;i;i--){
		for(int j=0;j<all;j++){
			g[i][j]=(g[i][j]+g[i+1][j])%mod;
			g[i][j|v[i]]=(g[i][j|v[i]]+1ll*g[i+1][j]*w[i]%mod)%mod;
		}
	}
	for(int i=0;i<=cnt;i++){
		fwt_or(f[i],1);fwt_or(g[i+1],1);
	}
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<all;j++){
			h[i][j]=1ll*f[i-1][j]*g[i+1][j]%mod;
		}
		fwt_or(h[i],-1);
	}
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<all;j++){
			if((j|v[i])==all-1) ans[i]=(ans[i]+h[i][j])%mod;
		}
		ans[i]=1ll*ans[i]*mi(2,s[v[i]]-1)%mod;
	}
	int x;
	scanf("%d",&qt);
	while(qt--){
		scanf("%d",&x);
		if(x%G){
			printf("0\n");
			continue;
		}
		x/=G;
		if(L%x||x>n){
			printf("0\n");
			continue;
		}
		int vt=lower_bound(v+1,v+cnt+1,gt(x))-v;
		printf("%d\n",ans[vt]);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值