[SDOI2015]序列统计

68 篇文章 0 订阅
47 篇文章 0 订阅

3992: [SDOI2015]序列统计

Time Limit: 30 Sec   Memory Limit: 128 MB
Submit: 1829   Solved: 870
[ 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)

HINT

Source

[ Submit][ Status][ Discuss]



FFT优化板子题,还有鬼畜的快速幂转移DP,更鬼畜的是我居然都1A了!


ACcode


#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 40005
using namespace std;

int n,m,s,x,len;
int B[maxn],F[maxn],Map[maxn];

int P=1004535809,G,invG,wn[2][22];

int pow(int base,int k,int P)
{
	int ret=1;
	for(;k;k>>=1,base=1ll*base*base%P) if(k&1) ret=1ll*ret*base%P;
	return ret;
}

bool check(int num,int P)
{
	for(int i=2;i*i<=P-1;i++)
		if((P-1)%i==0 && (pow(num,(P-1)/i,P)==1 || pow(num,i,P)==1))
			return 0;
	return 1;
}

void Prework()
{
	for(int i=2;;i++) 
		if(check(i,P))
		{
			G=i;
			break;
		}
	invG=pow(G,P-2,P);
	for(int i=1;i<=21;i++) wn[1][i]=pow(G,(P-1)/(1<<i),P),wn[0][i]=pow(invG,(P-1)/(1<<i),P);
	
	int GG;
	for(int i=2;;i++) 
		if(check(i,m))
		{
			GG=i;
			break;
		}
	for(int i=1,j=GG;i<=m-1;i++)
	{
		Map[j]=i;
		j=1ll*j*GG%m;
	}
}

int C[maxn],D[maxn];

void NTT(int *A,int n,int typ)
{
	for(int i=0,j=0,k;i<n;i++)
	{
		if(i<j) swap(A[i],A[j]);
		for(k=n>>1;k;k>>=1) if((j^=k)>=k) break;
	}
	
	for(int i=1,j,k,w,x,y,len;1<<i<=n;i++)
	{
		len=1<<(i-1);
		for(j=0;j<n;j+=1<<i)
		{
			w=1;
			for(k=0;k<len;k++,w=1ll*w*wn[typ][i]%P)
			{
				x=A[j+k],y=1ll*A[j+k+len]*w%P;
				A[j+k]=(1ll*x+y)%P;
				A[j+k+len]=(1LL*x-y)%P;
			}
		}
	}
	
	if(typ==0)
		for(int i=0,inv=pow(n,P-2,P);i<n;i++)
			A[i]=1ll*A[i]*inv%P;
}

void mul(int *ret,int *a,int *b,int len)
{
	for(int i=0;i<=len;i++)
		C[i]=a[i],D[i]=b[i];
	NTT(C,len,1);
	NTT(D,len,1);
	for(int i=0;i<=len;i++) ret[i]=1ll*C[i]*D[i]%P;
	NTT(ret,len,0);
	for(int i=m;i<=len;i++)
	{
		ret[i-m+1]=(1ll*ret[i-m+1]+ret[i])%P;
		ret[i]=0;
	}
}

int base[maxn],ans[maxn];
void pow(int *ret,int *A,int k,int len)
{
	ret[0]=1;
	for(int i=0;i<=len;i++) base[i]=A[i];
	for(;k;k>>=1,mul(base,base,base,len)) if(k&1) mul(ret,ret,base,len);
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&x,&s);
	Prework();
	for(len=1;len<=2*m;len<<=1);
	for(int i=1,tmp;i<=s;i++)
	{
		scanf("%d",&tmp);
		if(!tmp) continue;
		B[Map[tmp]]++;
	}
	
	pow(ans,B,n,len);
	printf("%d\n",(ans[Map[x]]+P)%P);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值