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 1≤n≤109, 3 ≤ m ≤ 8000 3\le m\le8000 3≤m≤8000,且 m m m 为质数, 1 ≤ x ≤ m − 1 1\le x\le m-1 1≤x≤m−1,保证集合 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∑∞[i∈S]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)=ij≡x(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+J≡x(modm−1)∑F(I)×F(J)
模 ( m − 1 ) (m-1) (m−1) 是因为费马小定律。
最后的答案显然就是 F n ( x ) F^n(x) Fn(x) 中指数为 log x \log x logx 的那一项,由于是循环卷积,每次要把 m − 1 ∼ 2 m − 3 m-1\sim 2m-3 m−1∼2m−3 的答案加到 0 ∼ m − 2 0\sim m-2 0∼m−2。
时间复杂度 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;
}