[BZOJ5019][FWT][状压DP]SNOI2017:遗失的答案

BZOJ5019

就是转化成求一堆数gcd的常用套路:考虑最小最大次幂
然后状压DP, f [ i ] [ j ] f[i][j] f[i][j]表示到第i个,状态为j这里最多600种转移,可以直接爆搜
如果我们正着DP一遍,反着DP一遍,那么合并就是or卷积:在正反中任意一个出现的必须保留
最后做一遍andfwt来回答询问

Code:

#include<bits/stdc++.h>
#define ll long long
#define db double
#define pb push_back
#define mod 1000000007
#define inv2 500000004
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void Dec(int &x,int y){x-=y;if(x<0) x+=mod;}
inline void Mul(int &x,int y){x=mul(x,y);}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
const int N=1e4+5,State=1<<16,M=600;
inline void fwt_or(int *a,int n,int kd){
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;++k)
				inc(a[i+j+k],kd==1?a[j+k]:dec(0,a[j+k]));
}
inline void fwt_and(int *a,int n,int kd){
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;++k)
				inc(a[j+k],kd==1?a[i+j+k]:dec(0,a[i+j+k]));
}
int pri[N],pt[N],cnt=0;
inline void init(int n){
	pt[1]=1;
	for(int i=2;i<=n;++i){
		if(!pt[i]) pri[++cnt]=i;
		for(int j=1;j<=cnt && i*pri[j]<=n;j++){
			pt[i*pri[j]]=1;
			if(i%pri[j]==0) break;
		}
	}
}
int lim[N],tot=0,pr[N];
inline void divide(int x){
	for(int i=1;i<=cnt && pri[i]*pri[i]<=x;++i){
		if(x%pri[i]==0){
			pr[++tot]=pri[i];
			while(x%pri[i]==0) x/=pri[i],++lim[tot];
		}
	}
	if(x>1) pr[++tot]=x,lim[tot]=1;
}
int trans[State];
int n,G,L;
void dfs(int v,int mlti,int S1,int S2){
	if(v==tot+1){++trans[S1|(S2<<tot)];return;}
	for(int i=0;i<=lim[v];++i){
		dfs(v+1,mlti,S1|((i==0)<<(v-1)),S2|((i==lim[v])<<(v-1)));
		if(1ll*mlti*pr[v]>n) return;
		mlti*=pr[v];
	}
}
inline int get(int x){
	int res=0;
	for(int i=1;i<=tot;++i){
		int cnt=0;
		while(x%pr[i]==0) x/=pr[i],++cnt;
		if(cnt==0) res|=(1<<(i-1));
		if(cnt==lim[i]) res|=(1<<(i-1+tot));
	}
	return res;
}
int f[M][State],g[M][State];
int dp[State],tmp[State];
int state[State],gcnt[State],scnt=0;
int main(){
	n=read();G=read();L=read();init(10000);
	int q=read();
	if(L%G){while(q--) puts("0");return 0;}
	L/=G,n/=G;divide(L);dfs(1,1,0,0);
	int all=1<<(tot*2);
	for(int i=0;i<all;++i) if(trans[i]) state[++scnt]=i,gcnt[scnt]=ksm(2,trans[i])-1;
	memcpy(trans,gcnt,sizeof(gcnt));
	f[0][0]=1;dp[0]=1;
	for(int i=1;i<=scnt;++i){
		for(int j=0;j<all;++j) inc(tmp[state[i]|j],mul(dp[j],trans[i]));
		for(int j=0;j<all;++j) inc(dp[j],tmp[j]),tmp[j]=0;
		for(int j=0;j<all;++j) f[i][j]=dp[j];
	}
	memset(dp,0,sizeof(dp));
	g[scnt+1][0]=1;dp[0]=1;
	for(int i=scnt;i;i--){
		for(int j=0;j<all;++j) inc(tmp[state[i]|j],mul(dp[j],trans[i]));
		for(int j=0;j<all;++j) inc(dp[j],tmp[j]),tmp[j]=0;
		for(int j=0;j<all;++j) g[i][j]=dp[j];
	}
	for(int i=0;i<=scnt;++i) fwt_or(f[i],all,1);
	for(int i=1;i<=scnt+1;++i) fwt_or(g[i],all,1);
	for(int i=0;i<=scnt;++i)
		for(int j=0;j<all;++j)
			f[i][j]=mul(f[i][j],g[i+2][j]);
	for(int i=0;i<=scnt;++i) fwt_or(f[i],all,-1),fwt_and(f[i],all,1);
	while(q--){
		int x=read();
		if(x%G){puts("0");continue;}
		x/=G;
		if(L%x || x>n) {puts("0");continue;}
		int d=get(x),ans=0;
		int p=lower_bound(state+1,state+scnt+1,d)-state-1;
		ans=f[p][(all-1)^d];
		Mul(ans,(trans[p+1]+1));
		cout<<mul(ans,inv2)<<"\n";
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值