1. 程式人生 > >LOJ #2183「SDOI2015」序列統計

LOJ #2183「SDOI2015」序列統計

有好多好玩的知識點

LOJ

題意:在集合中選$ n$個元素(可重複選)使得乘積模$ m$為$ x$,求方案數對$ 1004535809$取模

$ n<=10^9,m<=8000且是質數,集合大小不超過m$


$ Solution:$

我們先考慮改乘積為加和之後怎麼做

直接對於集合中的數構建生成函式

所要求的就是這個生成函式的$ n$次冪的所有模$ m$為$ c$的項的係數的和

用快速冪優化這個生成函式的$ n$次冪

每次乘法之後立刻把$ [m,2m)$的係數加回$[0,m)$

這樣可以保證每時每刻生成函式的長度不超過$ m$

就可以直接$ NTT$優化這個過程了

時間複雜度為$ O(m log m log n)$

 

然後考慮乘積的情況

我們知道$ x^ax^b=x^{a+b}$

嘗試把每個數改成某個底數的若干次方

由於$ m$是質數一定存在原根

原根有性質是它的$[0,phi(m))$在模$ m$意義下互不相同

這樣就可以直接把每個集合中的數改成$ m$的原根的若干次方就好了

然後就是加和情況的做法

複雜度不變

注意可能要特判原集合中存在$ 0$的情況


 $ my \ code$

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include
<iostream> #include<algorithm> #include<queue> #include<vector> #define p 1004535809 #define rt register int #define ll long long using namespace std; inline ll read(){ ll x = 0; char zf = 1; char ch = getchar(); while (ch != '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') zf = -1, ch = getchar(); while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf; } void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);} void writeln(const ll y){write(y);putchar('\n');} int i,j,k,m,n,x,y,z,cnt,c,yg; int a[100010],val[8555]; namespace NTT{ int ksm(int x,int y){ int ans=1; for(rt i=y;i;i>>=1,x=1ll*x*x%p)if(i&1)ans=1ll*x*ans%p; return ans; } vector<int>R; void NTT(int n,vector<int>&A,int fla){ A.resize(n); for(rt i=0;i<n;i++)if(i>R[i])swap(A[i],A[R[i]]); for(rt i=1;i<n;i<<=1){ int w=ksm(3,(p-1)/2/i); for(rt j=0;j<n;j+=i<<1){ int K=1; for(rt k=0;k<i;k++,K=1ll*K*w%p){ const int x=A[j+k],y=1ll*K*A[i+j+k]%p; A[j+k]=(x+y)%p,A[i+j+k]=(x-y)%p; } } } if(fla==-1){ reverse(A.begin()+1,A.end()); int invn=ksm(n,p-2); for(rt i=0;i<n;i++)A[i]=1ll*A[i]*invn%p; } } void mul(int del,int n,vector<int>&A){ NTT(n,A,1); for(rt i=0;i<n;i++)A[i]=1ll*A[i]*A[i]%p; NTT(n,A,-1); for(rt i=del;i<n;i++)(A[i%del]+=A[i])%=p,A[i]=0; } void calc(int n,vector<int>&A,int y,int pl){ int lim=1; while(lim<=n*2+2)lim<<=1; R.resize(lim);A.resize(lim); for(rt i=1;i<lim;i++)R[i]=(R[i>>1]>>1)|((i&1)*(lim>>1)); vector<int>ans;ans.resize(lim); ans[0]=1; for(rt i=y;i;i>>=1,mul(n,lim,A))if(i&1){ NTT(lim,ans,1);NTT(lim,A,1); for(rt j=0;j<lim;j++)ans[j]=1ll*ans[j]*A[j]%p; NTT(lim,ans,-1);NTT(lim,A,-1); for(rt j=n;j<lim;j++)(ans[j%n]+=ans[j])%=p,(A[j%n]+=A[j])%=p,ans[j]=0,A[j]=0; } writeln((ans[pl]+p)%p); } }; vector<int>A,B; using namespace NTT; int main(){ n=read();m=read();c=read();k=read(); A.resize(m+1); for(rt i=2;i<=m;i++){ for(rt j=1,k=i;j<m-1;j++,k=1ll*k*i%m)if(k==1)goto GG; yg=i;break; GG:; } for(rt i=0,j=1;i<m-1;i++,j=1ll*yg*j%m)val[j]=i; for(rt i=1;i<=k;i++){ x=read();if(x)A[val[x]]++; } calc(m-1,A,n,val[c]); return 0; }