P5366-[SNOI2017]遗失的答案【状压dp,FWT】

正题

题目链接:https://www.luogu.com.cn/problem/P5366


题目大意

给出一个\(n,G,L\)

\(q\)次询问在\(1\sim n\)中选择若干个数字并且数字\(x\)必选,要求这些数的\(gcd\)\(G\)\(lcm\)\(L\)的方案数。

\(1\leq n,G,L,x\leq 10^8,1\leq q\leq 10^5\)


解题思路

我们令\(m=\frac{L}{G},x=\frac{x}{G},n=\lfloor\frac{n}{G}\rfloor\),那么就是求选\(1\sim m\)中的数的情况下\(gcd=1,lcm=m\)的方案。

发现对于每个\(m\)的分解后的质因数\(p^c\),我们选择的数的为\(p^k\),那么我们至少需要一个\(k=0\)和一个\(k=c\)

也就是其实\(0<k<m\)的都是不会对这个质因数产生影响的,所以我们可以把一个质因子\(p\)分出两个状态,分别是\(k=0\)的和\(k=m\)的有没有。

同样的,我们用上面的状态\(S\)表示\(1\sim n\)的数字,会发现\(S\)最多只有六百出头种不同的取值。

那么我们把这些取值拿出来,把数字分成不同的类,这样我们就只需要考虑每个类的数字个数了。记\(f_{i,S}\)表示做完了前\(i\)类时状态为\(S\)的方案,同理\(g_{i,S}\)则表示做完了后\(i\)类。

这样我们可以用\(FWT\)快速处理出\(h_{i,S}\)表示处理了除了第\(i\)类以外的所有类,状态为\(S\)时的方案。

然后再考虑这一类有一个数字必选来转移每个\(h_{i,S}\)就知道每一类的答案了。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cctype>
using namespace std;
const int N=1<<16,M=610,P=1e9+7;
int n,G,L,q,m,tot,cnt,MS,num[N];
int p[99],c[99],pos[N],pw[M],rev[M];
int f[M][N],g[M][N],h[M][N];
int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-48;c=getchar();}
	return x*f;
}
int power(int x,int b){
	int ans=1;
	while(b){
		if(b&1)ans=1ll*ans*x%P;
		x=1ll*x*x%P;b>>=1;
	}
	return ans;
}
void FWT(int *f,int n,int op){
	for(int p=2;p<=n;p<<=1)
		for(int k=0,len=p>>1;k<n;k+=p)
			for(int i=k;i<k+len;i++)
				(f[i+len]+=f[i]*op)%=P;
	return;
}
void dfs(int dep,int x,int s){
	if(x>n)return;
	if(dep>cnt){num[s]++;return;}
	dfs(dep+1,x,s|(1<<dep+cnt-1));
	for(int i=1,pw=1;i<=c[dep];i++)
		pw=pw*p[dep],dfs(dep+1,x*pw,s|((i==c[dep])<<dep-1));
	return;
}
void init(){
	int x=m;
	for(int i=2;i<=x;i++)
		if(x%i==0){
			p[++cnt]=i;
			while(x%i==0)c[cnt]++,x/=i;
		}
	dfs(1,1,0);MS=(1<<cnt*2);
	for(int i=0;i<MS;i++)
		if(num[i]){
			pos[i]=++tot;rev[tot]=i;
			pw[tot]=power(2,num[i])-1;
		}
	f[0][0]=g[tot+1][0]=1;
	for(int i=1;i<=tot;i++)
		for(int j=0;j<MS;j++){
			(f[i][j]+=f[i-1][j])%=P;
			(f[i][j|rev[i]]+=1ll*f[i-1][j]*pw[i]%P)%=P;
		}
	for(int i=tot;i>=1;i--)
		for(int j=0;j<MS;j++){
			(g[i][j]+=g[i+1][j])%=P;
			(g[i][j|rev[i]]+=1ll*g[i+1][j]*pw[i]%P)%=P;
		}
	for(int i=0;i<=tot;i++)FWT(f[i],MS,1);
	for(int i=1;i<=tot+1;i++)FWT(g[i],MS,1);
	for(int i=1;i<=tot;i++)
		for(int j=0;j<MS;j++)
			h[i][j]=1ll*f[i-1][j]*g[i+1][j]%P;
	for(int i=1;i<=tot;i++){
		FWT(h[i],MS,-1);
		int k=power(2,num[rev[i]]-1);
		for(int j=MS-1;j>=0;j--){
			int r=h[i][j];h[i][j]=0;
			(h[i][j|rev[i]]+=1ll*r*k%P)%=P;
		}
	}
	return;
}
signed main()
{
	n=read();G=read();L=read();q=read();
	n/=G;m=L/G;init();
	while(q--){
		int x=read();
		if(x%G){puts("0");continue;}
		if(L%x){puts("0");continue;}
		x/=G;
		if(x>n){puts("0");continue;}
		int S=((1<<cnt)-1)*(1<<cnt);
		for(int i=1;i<=cnt;i++)
			if(x%p[i]==0){
				int k=0;S^=(1<<i+cnt-1);
				while(x%p[i]==0)x/=p[i],k++;
				if(k==c[i])S|=(1<<i-1);
			}
		cout<<(h[pos[S]][MS-1]+P)%P<<'\n';
	}
	return 0;
}
posted @ 2022-04-25 19:15  QuantAsk  阅读(43)  评论(0编辑  收藏  举报