BZOJ 3992: [SDOI2015]序列统计 倍增dp 原根 NTT

3992: [SDOI2015]序列统计

Time Limit: 30 Sec  Memory Limit: 128 MB
Submit: 1781  Solved: 842
[Submit][Status][Discuss]

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中的所有元素。
1<=N<=10^9,3<=M<=8000,M为质数
0<=x<=M-1,输入数据保证集合S中元素不重复

Output

一行,一个整数,表示你求出的种类数mod 1004535809的值。

Sample Input

4 3 1 2
1 2

Sample Output

8
【样例说明】
可以生成的满足要求的不同的数列有(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)

应该可以讲一眼秒掉吧。

一道疯狂拼接的组合套路题。

看到乘积取模 M 又是质数 直接想原根指标

之后 前两天刚做了两道 “倍增dp"(应该可以这么叫吧)

转移是卷积形式 NTT 优化一下就好了


不过 x==0 的时候 是不是得特判搞啊... 直接这样 为啥就过了...

LOJ上范围是 1<=x<=m-1 啊...


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=10*x+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=25010,mod=1004535809;

int n,M,X,S,G,lim;

inline int qpow(int x,int y,int P)
{
	int res(1);
	while(y)
	{
		if(y&1) res=1ll*res*x%P;
		x=1ll*x*x%P;
		y>>=1;
	}
	return res;
}

int I[N],to[N];

void get_g()
{
	vector<int> vec;
	register int i,tmp(M-1);
	for(i=2;i<tmp;++i)
	{
		if(tmp%i==0)
		{
			vec.push_back(i);
			while(tmp%i==0) tmp/=i;
		}
		if(tmp==1) break;
	}
	if(tmp>1) vec.push_back(tmp);
	tmp=M-1;G=1;
	while(1)
	{
		G++;
		bool flag(0);
		for(i=0;i<vec.size();++i)
			if(qpow(G,tmp/vec[i],M)==1)
			{flag=1;break;}
		if(!flag) break;
	}
	for(i=1,tmp=G;i<M-1;++i,tmp=tmp*G%M) I[tmp]=i;
}

int r[N];

void ntt(int *x,int opt)
{
	register int i,j,k,m,gn,g,tmp;
	for(i=0;i<lim;++i)
		if(r[i]<i)
			swap(x[i],x[r[i]]);
	for(m=2;m<=lim;m<<=1)
	{
		k=m>>1;
		gn=qpow(3,(mod-1)/m,mod);
		for(i=0;i<lim;i+=m)
		{
			g=1;
			for(j=0;j<k;++j,g=1ll*g*gn%mod)
			{
				tmp=1ll*x[i+j+k]*g%mod;
				x[i+j+k]=(x[i+j]-tmp+mod)%mod;
				x[i+j]=(x[i+j]+tmp)%mod;
			}
		}
	}
	if(opt==-1)
	{
		reverse(x+1,x+lim);
		register int inv=qpow(lim,mod-2,mod);
		for(i=0;i<lim;++i)
			x[i]=1ll*x[i]*inv%mod;
	}
}
	
int f[N],buc[N],B[N];

void mul(int *A,int *b)
{
	register int i;
	memcpy(B,b,sizeof(B));
	ntt(A,1);ntt(B,1);
	for(i=0;i<lim;++i)
		A[i]=1ll*A[i]*B[i]%mod;
	ntt(A,-1);
	for(i=M-1;i<(M<<1)-1;++i)
		(A[i%(M-1)]+=A[i])%=mod,A[i]=0;
}

void qpow()
{
	while(n)
	{
		if(n&1) mul(f,buc);
		mul(buc,buc);
		n>>=1;
	}
}

int main()
{
	n=read();M=read();X=read();S=read();
	get_g();
	register int i,x;
	while(S--)
	{
		x=read()%M;
		if(x) buc[I[x]]++;
	}
	f[0]=1;lim=1;
	while(lim<(M<<1)) lim<<=1;
	for(i=0;i<lim;++i)
		r[i]=(i&1)*(lim>>1)+(r[i>>1]>>1);
	qpow();
	cout<<f[I[X]]<<endl;
	return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值