BZOJ3992(NTT+DP+快速幂)

2019-04-14 12:11发布

题面
题意是给你一个质数M,和一个元素都小于M 的集合,大小为S,用集合中的元素构成长度为N的序列(可以用重复的元素),使其乘积模M为x,问方案数,答案模479*2^21+1。
M<=8000,N<=1e9 这题一看就很套路,模数为费马素数,大概就和NTT有关吧。
先考虑简单的DP,设f[i][j]为长度为i的序列,乘积模M等于j的方案数
枚举k,有
f[i+1][jkM]+=f[i][j] 看到M为质数,就必然存在原根,且集合中的元素都小于M,均可化成M的某次幂,且不重复,x同理。就把原问题转化成了一个长为N的序列,和为x的方案数,把乘法转换成了加法。有
f[i][j]=k=0jf[i1][k]f[i1][jk]+k=0j+M2f[i1][k]f[i1][j+M2k] 是一个卷积形式,就可以用NTT优化状态转移。
看到N<=1e9,每次转移过程都类似,可以用快速幂优化。相当于枚举前半段的和与后半段的和,NTT进行状态转移。 总复杂度Mlog(M)log(N)。 对于求质数P原根,我也不太懂,大概这样吧
听说原根一般不大,从2开始枚举原根x,枚举P-1的约数i,i!=p-1。若x^i%P=1,则x不是原根。 #include #include #include #include #include #include #include #include using namespace std; #define mmst(a, b) memset(a, b, sizeof(a)) #define mmcp(a, b) memcpy(a, b, sizeof(b)) typedef long long LL; const LL mo=1004535809,N=100100; int s,g,len,n,m,x,y; int rev[N],gg=3; LL ans[N],b[N],bb[N],little_busters[N]; LL cheng(LL a,LL b,LL p) { LL ans=1ll; for(;b;b>>=1,a=a*a%p) if(b&1) ans=ans*a%p; return ans; } bool ok(int x,int p) { for(int i=2;i*i<=p;i++) if((p-1)%i==0&&cheng(x, (p-1)/i,p)==1) return 0; return 1; } int find(int p) { if(p==2) return 1; int res=2; for(;!ok(res,p);) res++; return res; } void init(int lim) { n=1; int k=-1; while(n1,k++; for(int i=0;i>1] >> 1) | ((i&1)<void ntt(LL *a,bool ops) { for(int i=0;iif(ifor(int l=2;l<=n;l<<=1) { int m=l>>1; int wn; if(ops) wn=cheng(gg,(mo-1)/l,mo); else wn=cheng(gg,mo-1-(mo-1)/l,mo); for(int i=0;i1; for(int k=0;kif(!ops) { LL Inv=cheng(n,mo-2,mo); for(int i=0;ivoid kiss(LL *a,LL *c) { ntt(a,1); if(a!=c) { for(int i=0;i1); } for(int i=0;i0); for(int i=m-1;i1]=(a[i-m+1]+a[i])%mo; a[i]=0; } if(a!=c) for(int i=0;iint main() { cin>>len>>m>>x>>s; g=find(m); for(int i=1;i<=s;i++) { int u; scanf("%d",&u); bb[u]=1; } y=0; for(;cheng(g,y,m)!=x;) y++; for(int i=0;i1;i++) if(bb[cheng(g,i,m)]) b[i]=1; init(m+m); ans[0]=1; for(;len;len>>=1) { if(len&1) kiss(ans,b); kiss(b,b); } cout<return 0; }