【BZOJ3992】[SDOI2015]序列统计 NTT+多项式快速幂

【BZOJ3992】[SDOI2015]序列统计

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中的所有元素。

Output

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

Sample Input

4 3 1 2
1 2

Sample Output

8

HINT

【样例说明】

可以生成的满足要求的不同的数列有(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)。

【数据规模和约定】

对于10%的数据,1<=N<=1000;

对于30%的数据,3<=M<=100;

对于60%的数据,3<=M<=800;

对于全部的数据,1<=N<=109,3<=M<=8000,M为质数,1<=x<=M-1,输入数据保证集合S中元素不重复

题解:如果你早已深入理解生成函数,可以无视下面这段话:

“未学生成函数的时候,以为这种题就是将两个桶相乘,得到一个新的桶,桶里装的是方案数。了解生成函数后,发现就是讲桶中的每一位都看成多项式中的一个系数,然后用多项式的运算法则来优化运算的过程,最后的答案依旧是其中的一位系数。如果像我一样对生成函数了解较少的话,可以先考虑DP,用DP方程将式子列出来,然后将整个DP数组看成一个大多项式,继续推下去就好。”

如果你早已深入理解NTT,可以无视下面这段话:

“NTT与FFT的区别是:FFT利用的是e的特性,将系数表达式与点值表达式进行快速的转换,而在NTT中,模数的原根正好有同样的性质,并且常见的就是998244353的一个原根=3。于是,只需要将e变成3,除法改成逆元,其余都一样了。”

如果你早已理解原根与指标,可以无视下面这段话:

“如果x^0,x^1,...x^n-1在mod n意义下正好覆盖了0-n-1中的所有数,则x是n的一个原根,他的意义可以看成是模意义下的e。而指标的意义,可以看成是模意义下的取ln。这两个东西在本题中的意义就是将乘法转变成加法。
“原根的求法:暴力枚举x,如果x对于$\varphi(p)$的所有质因子pi,都有$x^{\varphi(p) \over pi} \neq 1$,则x是p的原根。
“指标的求法:如果原根是r,则r^x的指标即为x。"

回到本题,我们将原数组求指标后,将得到的多项式^n即可,可以用多项式的快速幂实现。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P=1004535809ll;
const ll G=3;
const int maxn=100010;
int n,m,X,S,root,num,len;
ll pri[maxn],A[maxn],B[maxn];
ll s[maxn],ind[maxn];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
ll pm(ll x,ll y,ll z)
{
	ll ret=1;
	while(y)
	{
		if(y&1)	ret=ret*x%z;
		x=x*x%z,y>>=1;
	}
	return ret;
}
void get_factor(ll x)
{
	for(ll i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			pri[++num]=i;
			while(x%i==0)	x/=i;
		}
	}
	if(x!=1)	pri[++num]=x;
}
bool check(ll x)
{
	for(int i=1;i<=num;i++)	if(pm(x,(m-1)/pri[i],m)==1)	return 0;
	return 1;
}
ll get_root(ll x)
{
	ll tmp=x-1;
	get_factor(tmp);
	for(ll i=2;i<=tmp;i++)	if(check(i))	return i;
	return 0;
}
void NTT(ll *a,int f)
{
	int i,j,k,h;
	ll t;
	for(i=k=0;i<len;i++)
	{
		if(i>k)	swap(a[i],a[k]);
		for(j=len>>1;(k^=j)<j;j>>=1);
	}
	for(h=2;h<=len;h<<=1)
	{
		ll wn=pm(G,f==1?(P-1)/h:P-1-(P-1)/h,P);
		for(j=0;j<len;j+=h)
		{
			ll w=1;
			for(k=j;k<j+h/2;k++)	t=w*a[k+h/2]%P,a[k+h/2]=(a[k]-t+P)%P,a[k]=(a[k]+t)%P,w=w*wn%P;
		}
	}
	if(f==-1)
	{
		t=pm(len,P-2,P);
		for(i=0;i<len;i++)	a[i]=a[i]*t%P;
	}
}
void POW(ll *b,ll y)
{
	ll *a=B;
	a[0]=1;
	while(y)
	{
		NTT(b,1);
		if(y&1)
		{
			NTT(a,1);
			for(int i=0;i<len;i++)	a[i]=a[i]*b[i]%P;
			NTT(a,-1);
			for(int i=len-1;i>=m-1;i--)	a[i-m+1]=(a[i-m+1]+a[i])%P,a[i]=0;
		}
		for(int i=0;i<len;i++)	b[i]=b[i]*b[i]%P;
		NTT(b,-1);
		for(int i=len-1;i>=m-1;i--)	b[i-m+1]=(b[i-m+1]+b[i])%P,b[i]=0;
		y>>=1;
	}
}
int main()
{
	n=rd(),m=rd(),X=rd(),S=rd();
	int i;
	for(i=1;i<=S;i++)	s[i]=rd();
	root=get_root(m);
	ll tmp=1;
	for(i=0;i<m-1;i++)	ind[tmp]=i,tmp=tmp*root%m;
	for(len=1;len<=m+m;len<<=1);
	for(i=1;i<=S;i++)	if(s[i])	A[ind[s[i]]]=1;
	POW(A,n);
	printf("%lld\n",B[ind[X]]);
	return 0;
}
时间: 2024-12-28 16:22:51

【BZOJ3992】[SDOI2015]序列统计 NTT+多项式快速幂的相关文章

BZOJ 3992: [SDOI2015]序列统计 NTT+快速幂

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1155  Solved: 532[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

[bzoj3992][SDOI2015]序列统计——离散对数+NTT

题目大意: 给定一个数字不超过\(m\)的集合\(S\),用\(S\)中的数生成一个长度为\(n\)的序列,求所有序列中的元素乘积模\(m\)等于\(x\)的序列的个数. 思路: 考虑最朴素的\(DP\),设\(f_{i,j}\)为选了\(i\)个数,乘积模\(m\)余\(j\)的方案数,直接转移的时间复杂度是\(O(nm^2)\)的. 不难发现每次转移的过程是相同的,矩阵加速显然不太可行,考虑将乘法形式的转移变成加法形式的转移,这样每次转移即可用NTT优化. 这里需要用到一个叫做离散对数的东西

bzoj3992 [SDOI2015]序列统计

Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个.小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi.另外,小C认为这个问题 的答案可能很大,因此他只需要你帮助他求出答案mod 100

BZOJ3992 [SDOI2015]序列统计 【生成函数 + 多项式快速幂】

题目 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数 列,数列中的每个数都属于集合S.小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助: 给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个.小C认为 ,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi.另外,小C认为这个问题的答案可能很大 ,因此他只需要你帮助他求出答案mod 1004535809

【动态规划】bzoj3992 [Sdoi2015]序列统计 10分

#include<cstdio> using namespace std; #define MOD 1004535809 int a[8001],f[1001][101],n,m,x,S; int main() { scanf("%d%d%d%d",&n,&m,&x,&S); for(int i=1;i<=S;++i) { scanf("%d",&a[i]); a[i]%=m; ++f[1][a[i]]; }

BZOJ 3992: [SDOI2015]序列统计 快速幂+NTT(离散对数下)

3992: [SDOI2015]序列统计 Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个.小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi.另外,小C认为这个问题的答案可能很大,因

BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 466[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

BZOJ 3992 Sdoi2015 序列统计 快速数论变换

题目大意:给定n(n<=10^9),质数m(3<=m<=8000),1<=x=m,以及一个[0,m-1]区间内的集合S,求有多少长度为n的数列满足每个元素都属于集合S且所有元素的乘积mod m后=x 求原根,对S集合内每个元素取指标,然后搞出生成函数f(x) 那么答案就是(f(x))^n (mod x^(m-1),mod 1004535809) 上NTT用多项式快速幂搞一搞就好了 #include <cstdio> #include <cstring> #i

LuoGuP3321:[SDOI2015]序列统计

Pre 错误百出. 第一次打多项式快速幂. Solution 可以发现用多项式优化动态规划的转移. 每加入一个数,就乘上一个多项式(其实这个多项式有一点像生成函数,指数表示的数模意义下的值,系数表示的是方案的数量). 这样就可以用多项式快速幂优化了. 于是我就\(WA\)了一发. 设\(f(i,j)\)表示已经有\(i\)个数,并且指数为\(j\)的时候的系数值. \(f(i,j)-\sum\limits_{m*n=j}f(i-1,m)*f(i-1,n)\) 于是貌似不可做. 我尝试着从修改\(