【BZOJ3992】[SDOI2015]序列統計 NTT+多項式快速冪
【BZOJ3992】[SDOI2015]序列統計
Description
小C有一個集合S,裏面的元素都是小於M的非負整數。他用程序編寫了一個數列生成器,可以生成一個長度為N的數列,數列中的每個數都屬於集合S。 小C用這個生成器生成了許多這樣的數列。但是小C有一個問題需要你的幫助:給定整數x,求所有可以生成出的,且滿足數列中所有數的乘積mod M的值等於x的不同的數列的有多少個。小C認為,兩個數列{Ai}和{Bi}不同,當且僅當至少存在一個整數i,滿足Ai≠Bi。另外,小C認為這個問題的答案可能很大,因此他只需要你幫助他求出答案mod 1004535809的值就可以了。Input
一行,四個整數,N、M、x、|S|,其中|S|為集合S中元素個數。第二行,|S|個整數,表示集合S中的所有元素。
Output
一行,一個整數,表示你求出的種類數mod 1004535809的值。
Sample Input
4 3 1 21 2
Sample Output
8HINT
【樣例說明】 可以生成的滿足要求的不同的數列有(1,1,1,1)、(1,1,2,2)、(1,2,1,2)、(1,2,2,1)、(2,1,1,2)、(2,1,2,1)、(2,2,1,1)、(2,2,2,2)。 【數據規模和約定】 對於10%的數據,1<=N<=1000; 對於30%的數據,3<=M<=100; 對於60%的數據,3<=M<=800; 對於全部的數據,1<=N<=109,3<=M<=8000,M為質數,1<=x<=M-1,輸入數據保證集合S中元素不重復題解
“未學生成函數的時候,以為這種題就是將兩個桶相乘,得到一個新的桶,桶裏裝的是方案數。了解生成函數後,發現就是講桶中的每一位都看成多項式中的一個系數,然後用多項式的運算法則來優化運算的過程,最後的答案依舊是其中的一位系數。如果像我一樣對生成函數了解較少的話,可以先考慮DP,用DP方程將式子列出來,然後將整個DP數組看成一個大多項式,繼續推下去就好。”
如果你早已深入理解NTT,可以無視下面這段話:
“NTT與FFT的區別是:FFT利用的是e的特性,將系數表達式與點值表達式進行快速的轉換,而在NTT中,模數的原根正好有同樣的性質,並且常見的就是998244353的一個原根=3。於是,只需要將e變成3,除法改成逆元,其余都一樣了。
如果你早已理解原根與指標,可以無視下面這段話:
“如果x^0,x^1,...x^n-1在mod n意義下正好覆蓋了0-n-1中的所有數,則x是n的一個原根,他的意義可以看成是模意義下的e。而指標的意義,可以看成是模意義下的取ln。這兩個東西在本題中的意義就是將乘法轉變成加法。
“原根的求法:暴力枚舉x,如果x對於$\varphi(p)$的所有質因子pi,都有$x^{\varphi(p) \over pi} \neq 1$,則x是p的原根。
“指標的求法:如果原根是r,則r^x的指標即為x。"
回到本題,我們將原數組求指標後,將得到的多項式^n即可,可以用多項式的快速冪實現。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; typedef long long ll; const ll P=1004535809ll; const ll G=3; const int maxn=100010; int n,m,X,S,root,num,len; ll pri[maxn],A[maxn],B[maxn]; ll s[maxn],ind[maxn]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<‘0‘||gc>‘9‘) {if(gc==‘-‘)f=-f; gc=getchar();} while(gc>=‘0‘&&gc<=‘9‘) ret=ret*10+gc-‘0‘,gc=getchar(); return ret*f; } ll pm(ll x,ll y,ll z) { ll ret=1; while(y) { if(y&1) ret=ret*x%z; x=x*x%z,y>>=1; } return ret; } void get_factor(ll x) { for(ll i=2;i*i<=x;i++) { if(x%i==0) { pri[++num]=i; while(x%i==0) x/=i; } } if(x!=1) pri[++num]=x; } bool check(ll x) { for(int i=1;i<=num;i++) if(pm(x,(m-1)/pri[i],m)==1) return 0; return 1; } ll get_root(ll x) { ll tmp=x-1; get_factor(tmp); for(ll i=2;i<=tmp;i++) if(check(i)) return i; return 0; } void NTT(ll *a,int f) { int i,j,k,h; ll t; for(i=k=0;i<len;i++) { if(i>k) swap(a[i],a[k]); for(j=len>>1;(k^=j)<j;j>>=1); } for(h=2;h<=len;h<<=1) { ll wn=pm(G,f==1?(P-1)/h:P-1-(P-1)/h,P); for(j=0;j<len;j+=h) { ll w=1; for(k=j;k<j+h/2;k++) t=w*a[k+h/2]%P,a[k+h/2]=(a[k]-t+P)%P,a[k]=(a[k]+t)%P,w=w*wn%P; } } if(f==-1) { t=pm(len,P-2,P); for(i=0;i<len;i++) a[i]=a[i]*t%P; } } void POW(ll *b,ll y) { ll *a=B; a[0]=1; while(y) { NTT(b,1); if(y&1) { NTT(a,1); for(int i=0;i<len;i++) a[i]=a[i]*b[i]%P; NTT(a,-1); for(int i=len-1;i>=m-1;i--) a[i-m+1]=(a[i-m+1]+a[i])%P,a[i]=0; } for(int i=0;i<len;i++) b[i]=b[i]*b[i]%P; NTT(b,-1); for(int i=len-1;i>=m-1;i--) b[i-m+1]=(b[i-m+1]+b[i])%P,b[i]=0; y>>=1; } } int main() { n=rd(),m=rd(),X=rd(),S=rd(); int i; for(i=1;i<=S;i++) s[i]=rd(); root=get_root(m); ll tmp=1; for(i=0;i<m-1;i++) ind[tmp]=i,tmp=tmp*root%m; for(len=1;len<=m+m;len<<=1); for(i=1;i<=S;i++) if(s[i]) A[ind[s[i]]]=1; POW(A,n); printf("%lld\n",B[ind[X]]); return 0; }
【BZOJ3992】[SDOI2015]序列統計 NTT+多項式快速冪