【SDOI 2015】序列统计

传送门


problem

小C有一个集合 S S S,里面的元素都是小于 m m m 的非负整数。他写了一个数列生成器,可以生成一个长度为 n n n 的数列,数列中的每个数都属于集合 S S S

小C有一个问题需要你的帮助:给定整数 x x x,求所有可以生成出的,且满足数列中所有数的乘积   m o d     m \bmod\; m modm 的值等于 x x x 的不同的数列的有多少个。

小C认为,两个数列 { a i } \{a_i\} {ai} { b i } \{b_i\} {bi} 不同,当且仅当至少存在一个整数 i i i,满足 a i ≠ b i a_i\ne b_i ai=bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案   m o d     1004535809 \bmod\; 1004535809 mod1004535809 的值就可以了。

数据规模与约定: 1 ≤ n ≤ 1 0 9 1\le n\le10^9 1n109 3 ≤ m ≤ 8000 3\le m\le8000 3m8000,且 m m m 为质数, 1 ≤ x ≤ m − 1 1\le x\le m-1 1xm1,保证集合 S S S 中元素不重复。


solution

先写出这个集合生成函数:

F ( x ) = ∑ i = 0 ∞ [ i ∈ S ] x i F(x)=\sum_{i=0}^{\infty}[i\in S]x^i F(x)=i=0[iS]xi

我们设 G ( x ) G(x) G(x) 表示从 S S S 中选出两个数能够生成的满足条件的序列个数,则有:

G ( x ) = ∑ i j ≡ x (   m o d    m ) F ( i ) × F ( j ) G(x)=\sum_{ij\equiv x(\,\mathrm{mod}\; m)}F(i)\times F(j) G(x)=ijx(modm)F(i)×F(j)

发现如果把 i j = x ij=x ij=x 改成 i + j = x i+j=x i+j=x,那就可以 f f t fft fft 了。然后就想一想可以把乘法改成加法的运算,发现对数就可以。

由于是模意义下的对数,那直接用原根处理即可。

I = log ⁡ i , J = log ⁡ j I=\log i,J=\log j I=logi,J=logj 的话,就有:

G ( x ) = ∑ I + J ≡ x (   m o d    m − 1 ) F ( I ) × F ( J ) G(x)=\sum_{I+J\equiv x(\,\mathrm{mod}\; m-1)}F(I)\times F(J) G(x)=I+Jx(modm1)F(I)×F(J)

( m − 1 ) (m-1) (m1) 是因为费马小定律

最后的答案显然就是 F n ( x ) F^n(x) Fn(x) 中指数为 log ⁡ x \log x logx 的那一项,由于是循环卷积,每次要把 m − 1 ∼ 2 m − 3 m-1\sim 2m-3 m12m3 的答案加到 0 ∼ m − 2 0\sim m-2 0m2

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define N 50005
#define P 1004535809
using namespace std;
const int G=3;
typedef vector<int> poly;
int n,m,X,S,g,pos[N],Log[N];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y%P;}
int power(int a,int b,int p,int ans=1){
	for(;b;b>>=1,a=1ll*a*a%p)
		if(b&1)  ans=1ll*ans*a%p;
	return ans;
}
int tot=0,factor[15];
bool check(int x){
	for(int i=1;i<=tot;++i)
		if(power(x,(m-1)/factor[i],m)==1)
			return false;
	return true;
}
int find(){
	int phi=m-1;
	for(int i=2;i*i<=m;++i)
		while(phi%i==0)  factor[++tot]=i,phi/=i;
	if(phi!=1)  factor[++tot]=phi;
	for(int i=1;;++i)
		if(check(i))  return i;
}
int *w[22],C=21;
void prework(){
	for(int i=1;i<=C;++i)
		w[i]=new int[1<<(i-1)];
	int now=power(G,(P-1)/(1<<C),P);
	w[C][0]=1;
	for(int i=1;i<(1<<(C-1));++i)  w[C][i]=mul(w[C][i-1],now);
	for(int i=C-1;i;--i)
		for(int j=0;j<(1<<(i-1));++j)
			w[i][j]=w[i+1][j<<1];
}
void init(int lim){
	for(int i=0;i<lim;++i)
		pos[i]=(pos[i>>1]>>1)|((i&1)*(lim>>1));
}
void NTT(poly &f,int lim,int type){
	for(int i=0;i<lim;++i)
		if(pos[i]>i)  swap(f[i],f[pos[i]]);
	for(int mid=1,l=1;mid<lim;mid<<=1,++l){
		for(int i=0;i<lim;i+=(mid<<1)){
			for(int j=0;j<mid;++j){
				int p0=f[i+j],p1=mul(f[i+j+mid],w[l][j]);
				f[i+j]=add(p0,p1),f[i+j+mid]=dec(p0,p1);
			}
		}
	}
	if(type==-1&&(reverse(f.begin()+1,f.begin()+lim),1)){
		int inv=power(lim,P-2,P);
		for(int i=0;i<lim;++i)  f[i]=mul(f[i],inv);
	}
}
poly operator*(poly A,poly B){
	int lim=1,len=A.size()+B.size()-2;
	while(lim<=len)  lim<<=1;init(lim);
	A.resize(lim),NTT(A,lim,1);
	B.resize(lim),NTT(B,lim,1);
	for(int i=0;i<lim;++i)  A[i]=mul(A[i],B[i]);
	NTT(A,lim,-1);
	for(int i=0;i<m-1;++i)  A[i]=add(A[i],A[i+m-1]);
	A.resize(m-1);return A;
}
int main(){
	prework();
	scanf("%d%d%d%d",&n,&m,&X,&S),g=find();
	poly A(m,0),ans(m,0);
	for(int i=0,p=1;i<m-1;++i,p=p*g%m)  Log[p]=i;  
	for(int i=1,x;i<=S;++i){
		scanf("%d",&x);
		if(x)  A[Log[x]]++;
	}
	ans[0]=1;
	for(;n;n>>=1,A=A*A)  if(n&1)  ans=ans*A;
	printf("%d",ans[Log[X]]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值