[SNOI2017]遗失的答案

遗失的答案

题解

其实这题挺水的。

显然,当 G ∤     L G\not | \,\,\,L GL时是无解的。我们可以先将小于 n n n的不是 G G G倍数的值全部舍去,只用管为 G G G倍数的数。
当出去 G G G后,剩下的数的最大公约数一定是 1 1 1,最小公倍数一定是 L G \frac{L}{G} GL。我们先将 L G \frac{L}{G} GL进行质因数分解,记 L G = ∏ p i a i \frac{L}{G}=\prod p_{i}^{a_{i}} GL=piai
容易发现,对于每个 p i p_{i} pi,一定有数不是 p i p_{i} pi的倍数,一定有数是 p i a i p_{i}^{a_{i}} piai的倍数,且所有数含有的 p i p_{i} pi个数一定在 [ 0 , a i ] [0,a_{i}] [0,ai]之间。

又因为, L ≤ 1 0 8 L\leq 10^8 L108,故不同 p i p_{i} pi的个数不会超过 8 8 8。于是我们就想到了状压。
我们需要同时记录下两个状态 ( S 1 , S 2 ) (S1,S2) (S1,S2),其中 S 1 S1 S1表示不含 p i p_{i} pi S 2 S2 S2表示含 p i a i p_{i}^{a_{i}} piai,我们将 S 1 S 2 S1S2 S1S2拼在一起,记作 S S S
容易发现,每个是 G G G倍数也是 L L L因数的数都可以用一个状态 S S S表示,而因为如果它的含有 p i p_{i} pi个数在 ( 0 , a i ) (0,a_{i}) (0,ai)中的话不会影响状态,所以我们可以先将这样的数全部统计一下,用 t S t_{S} tS表示达到状态为 S S S的不选状态 S S S以外的数的选数方法的个数。
容易发现不同的 S S S个数是不会超过 600 600 600的(这里指的因数只考虑 p i p_{i} pi的次数满的与空的两种情形),可以通过暴力进行证明,我们将这样的 S S S记录下来。

我们可以先设出两个 d p dp dp f i , S f_{i,S} fi,S表示选完前 i i i个状态,现在的总状态为 S S S时的方案数。
同样的, g i , S g_{i,S} gi,S表示选完后 i i i个状态,现在的总状态为 S S S时的方案数。
至于如何处理必须选一个数的方案数,我们可以先将这个数的状态求出来,求出在这个状态满足其的数少了一个的情况下的 d p dp dp即可。
由于我们已经处理过前缀与后缀,我们可以将其两边的 d p dp dp合起来,再加上少了一个的方案数。
由于合起来有涉及到状态的按位或,有 a n s i , j = ∑ x ∣ y = j f i − 1 , x g i + 1 , y ans_{i,j}=\sum_{x|y=j}f_{i-1,x}g_{i+1,y} ansi,j=xy=jfi1,xgi+1,y
容易发现,这就是一个可以用 F W T FWT FWT解决的板子式子,可以通过 F W T FWT FWT将这个问题简单的解决。

m m m为不同的状态数,时间复杂度为 O ( n m l o g   n ) O\left(nmlog\,n\right) O(nmlogn)

题解

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 70005
#define MAXM 655
#define reg register
typedef long long LL;
const int mo=1e9+7;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
inline int add(const int x,const int y){return x+y<mo?x+y:x+y-mo;}
inline void Add(int &x,const int y){x=add(x,y);}
inline int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
int n,G,L,q,prime[20],cnt[20],cntp,idx,res[MAXN],ans[MAXM][MAXN];
int s[MAXN],num[MAXN],t[MAXN],f[MAXM][MAXN],g[MAXM][MAXN];
inline void devide(int x){
	for(reg int i=2;i*i<=x;++i)
		if(x%i==0){
			prime[++cntp]=i;cnt[cntp]=0;
			while(x%i==0)x/=i,++cnt[cntp];
		}
	if(x>1)prime[++cntp]=x,cnt[cntp]=1;
}
void dfs(const int dep,int cur,const int s1,const int s2){
	if(dep>cntp)return (void)(++s[s1|(s2<<cntp)]);
	for(reg int i=0;i<=cnt[dep];++i){
		dfs(dep+1,cur,s1|((i==0)<<dep-1),s2|((i==cnt[dep])<<dep-1));
		cur*=prime[dep];if(cur>n)return ;
	}
}
inline void FWT(int *A,const int lim,const int typ){
	for(reg int k=1;k<lim;k<<=1)
		for(reg int i=0;i<lim;i+=(k<<1))
			for(reg int j=i;j<i+k;++j)
				Add(A[j+k],typ?A[j]:mo-A[j]);
}
inline int calc(int x){
	int s1=0,s2=0;
	for(reg int i=1;i<=cntp;++i){
		int s=0;while(x%prime[i]==0)x/=prime[i],s++;
		s1|=(s==0)<<i-1,s2|=(s==cnt[i])<<i-1;
	}
	return s1|(s2<<cntp);
}
signed main(){
	read(n);read(G);read(L);read(q);
	if(L%G){while(q--)puts("0");return 0;}
	n/=G;L/=G;devide(L);dfs(1,1,0,0);const int lim=(1<<cntp+cntp);
	for(reg int i=0;i<lim;++i)if(s[i])num[++idx]=i,t[idx]=qkpow(2,s[i])-1;
	f[0][0]=g[idx+1][0]=1;
	for(reg int i=0;i<idx;++i)
		for(reg int j=0;j<lim;++j)
			Add(f[i+1][j],f[i][j]),Add(f[i+1][j|num[i+1]],1ll*f[i][j]*t[i+1]%mo);
	for(reg int i=idx+1;i>1;--i)
		for(reg int j=0;j<lim;++j)
			Add(g[i-1][j],g[i][j]),Add(g[i-1][j|num[i-1]],1ll*g[i][j]*t[i-1]%mo);
	for(reg int i=0;i<=idx+1;++i)FWT(f[i],lim,1),FWT(g[i],lim,1);
	for(reg int i=1;i<=idx;++i)
		for(reg int j=0;j<lim;++j)
			Add(ans[i][j],1ll*f[i-1][j]*g[i+1][j]%mo);
	for(reg int i=1;i<=idx;++i)FWT(ans[i],lim,0);
	for(reg int i=1;i<=idx;++i){
		for(reg int j=0;j<lim;++j)
			if((j|num[i])==lim-1)Add(res[i],ans[i][j]);
		res[i]=1ll*res[i]*qkpow(2,s[num[i]]-1)%mo;
	}
	for(reg int i=1;i<=q;++i){
		int x;read(x);if(x%G){puts("0");continue;}
		x/=G;if(L%x||x>n){puts("0");continue;}
		const int y=calc(x),p=lower_bound(num+1,num+idx+1,y)-num;
		printf("%d\n",res[p]);
	}
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值