1. 程式人生 > >【BZOJ3992】[SDOI2015]序列統計 NTT+多項式快速冪

【BZOJ3992】[SDOI2015]序列統計 NTT+多項式快速冪

繼續 -m zoj 幫助 cst div sam [0 程序

【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 2
1 2

Sample Output

8

HINT

【樣例說明】 可以生成的滿足要求的不同的數列有(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+多項式快速冪