BZOJ3992 [SDOI2015]序列统计

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

 

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

题目链接:BZOJ3992

正解:$NTT$+原根

解题报告:  

  考虑朴素$DP$:令$f[i][j]$表示序列的前$i$位的乘积取模为$j$的方案数,那么转移就很$simple$了,就是每次枚举集合内的一个数然后直接转出就好了,这个的复杂度是$O(nm^2)$的。

  考虑这个乘法的过程很不资瓷啊,不能优化呀,如果变成加法了就可以变成卷积的形式了呀。

  这是一类巧妙转化:把乘法变成加法卷积->利用原根的性质。考虑$m$为质数,那么必然存在原根$g$,而$m$以内的每个数都可以表示成$g$的次幂,然后就变成加法辣!因为要取模,直接上$NTT$,但还是$O(nmlogm)$的呀,$n$巨大无比...

  每次转移的多项式都是一样的,直接快速幂就好了。

 

   补一发原根的求法:

  暴力的话就是枚举原根然后按照定义$check$就好了,

  还有一种做法,就是把$p-1$质因数分解之后,对于$g^{(p-1)/质因子}$$check$一下,如果存在一个为$1$就不是原根,这样的话因为原根一般比较小而且复杂度很优秀,所以能有理有据的跑过去...

 

//It is made by ljh2000
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <complex>
#include <vector>
#include <cstdio>
#include <string>
#include <bitset>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define lc root<<1
#define rc root<<1|1
#define pr pair<int,int>
#define MP make_pair
#define fr first
#define sc second
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define per(i,j,k) for(int i=j;i>=k;--i)
#define reg(i,x) for(int i=first[x];i;i=next[i])
using namespace std;
typedef long long LL;
typedef long double LB;
typedef complex<double> C;
const double pi = acos(-1);
const double eps = 1e-9;
const int MAXN = 20011;
const int mod = 1004535809;
int middle[MAXN];
inline int fast_pow(int x,int y,int mm){ int r=1; while(y>0) { if(y&1) r=1LL*r*x%mm; x=1LL*x*x%mm; y>>=1; } return r; }
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

namespace NTT{
	const int G = 3;
	int n,m,a[MAXN],b[MAXN],L,R[MAXN];
	inline void DFT(int *a,int n,int f){
		rep(i,0,n-1) if(i<R[i]) swap(a[i],a[R[i]]);
		int wn,w,x,t;
		for(int i=1;i<n;i<<=1) {
			wn=fast_pow(G,(mod-1)/(i<<1),mod);
			for(int j=0;j<n;j+=(i<<1)) {
				w=1;
				for(int k=0;k<i;k++,w=1LL*w*wn%mod) {
					x=a[j+k]; t=1LL*a[j+i+k]*w%mod;
					a[j+k]=(x+t)%mod;
					a[j+i+k]=(x-t+mod)%mod;
				}
			}
		}
		if(f==1) return ;
		reverse(a+1,a+n);
	}

	inline void work(){
		int savn=n,savm=m;
		m+=n; for(L=0,n=1;n<=m;n<<=1) L++;
		rep(i,0,n-1) R[i]=(R[i>>1]>>1) | ((i&1) << (L-1));
		rep(i,savn+1,n) a[i]=0;
		rep(i,savm+1,n) b[i]=0;
		rep(i,savm+1,n) middle[i]=0;
		DFT(a,n,1); DFT(b,n,1);
		rep(i,0,n) a[i]=1LL*a[i]*b[i]%mod;
		DFT(a,n,-1); int ni=fast_pow(n,mod-2,mod);
		rep(i,0,m) middle[i]=1LL*a[i]*ni%mod;
		rep(i,savm+1,m) middle[i-savm]+=middle[i],middle[i-savm]%=mod;
	}
}

int n,m,final,S,mi[MAXN],rt,match[MAXN];
int pri[MAXN],cc,a[MAXN],ans[MAXN];
bool hav0;
inline void getrt(){
	if(m==2) { rt=1; return ; }
	int x=m-1;
	for(int i=2;i*i<m;i++) {
		if(x%i==0) {
			pri[++cc]=i;
			while(x%i==0) x/=i;
			if(x==1) break;
		}
	}
	if(x>1) pri[++cc]=x;

	bool flag;
	for(rt=2;;rt++) {
		flag=true;
		for(int i=1;i<=cc;i++)
			if(fast_pow(rt,(m-1)/pri[i],m)==1) { flag=false; break; }
		if(flag) return ;
	}
}

inline void work(){
	n=getint(); m=getint(); final=getint(); S=getint(); int x;
	getrt(); mi[0]=1; rep(i,1,m-1) mi[i]=1LL*mi[i-1]*rt%m,match[mi[i]]=i;
	rep(i,1,S) { x=getint(); if(x!=0) a[match[x]]=1; else hav0=true; }
	if(final==0) { if(!hav0) puts("0"); else printf("%d",(fast_pow(S,n,mod)-fast_pow(S-1,n,mod)+mod)%mod); return ; }

	ans[0]=1; int nn=0;
	while(n>0) {
		if(n&1) {
			rep(i,0,nn) NTT::a[i]=ans[i];
			rep(i,0,m-1) NTT::b[i]=a[i];
			NTT::n=nn;
			NTT::m=m-1;
			NTT::work();
			nn=m-1; nn%=m;
			rep(i,0,m-1) ans[i]=middle[i];
		}
		NTT::n=m-1; NTT::m=m-1;
		rep(i,0,m-1) NTT::a[i]=a[i];
		rep(i,0,m-1) NTT::b[i]=a[i];
		NTT::work();
		rep(i,0,m-1) a[i]=middle[i];
		n>>=1;
	}

	printf("%d",ans[match[final]]);
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("3992.in","r",stdin);
	freopen("3992.out","w",stdout);
#endif
    work();
    return 0;
}
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。

  

 

posted @ 2017-03-31 16:37  ljh_2000  阅读(227)  评论(0编辑  收藏  举报