SDOI2015 序列统计

本文深入探讨了模数998244353和1004535809在算法竞赛中的应用,特别是在生成函数和离散对数领域的关键作用。文章详细解释了如何利用这些特殊模数的特性进行快速傅立叶变换(FFT),并介绍了通过取对数将乘法转化为加法的技巧,以解决复杂序列问题。

传送门
参考博客
其实模数是一个很重要的东西。当你看到 998244353 998244353 998244353 1004535809 1004535809 1004535809时,不管怎么说,都要用生成函数试着思考一下,不然对不起这个模数啊。
998244353 = 2 119 ∗ 23 + 1 , g = 3 998244353=2^{119}*23+1,g=3 998244353=211923+1,g=3
1004535809 = 2 479 ∗ 21 + 1 , g = 3 1004535809=2^{479}*21+1,g=3 1004535809=247921+1,g=3
顺便复习一下,单位根可以做 N T T NTT NTT,是因为一个性质:
对于 P = 2 n ∗ k + 1 P=2^n*k+1 P=2nk+1,令 g t = g k g_t=g^k gt=gk,其中 g g g为模 P P P的原根, t = P − 1 k t=\frac{P-1}{k} t=kP1
这样就可以满足: g t 0 , g t 1 , ⋯   , g t t − 1 g_t^0,g_t^1,\cdots,g_t^{t-1} gt0,gt1,,gtt1互不相同且满足原根性质。参见此文

总之,遇见一个模数不是 1 e 9 + 7 1e9+7 1e9+7的题,就看有没有 m o d − 1 = 2 k ∗ p mod-1=2^k*p mod1=2kp这个性质。
当然,更重要的是对生成函数的理解。。

我们写出长度为1的序列的生成函数:
F ( x ) = ∑ i = 0 ∞ [ i ∈ S ] x i F(x)=\sum_{i=0}^{\infty}[i\in S]x^i F(x)=i=0[iS]xi
指数代表 序列乘积,系数代表 值为该乘积的方案数。
那么长度为2的写出来就是:
G ( x ) = ∑ t = 0 ∞ ∑ i ∗ j ≡ t   ( m o d   m ) ( [ x i ] F ∗ [ x j ] F ) x t G(x)=\sum_{t=0}^{\infty}\sum_{i*j \equiv t\ (mod\ m)}^{}{([x^i]F*[x^j]F)}x^t G(x)=t=0ijt (mod m)([xi]F[xj]F)xt
如果可以把乘法转化为加法,就做完了。
这又是一个常用套路:取对数。在模意义下也就是离散对数。
由于 m m m是一个质数,所以它有原根,令其为 g g g
又由于它是质数,所以 ∀ i ∈ [ 1 , m − 1 ] , ∃ p i : g p i ≡ i   ( m o d   m ) \forall i \in [1,m-1],\exists p_i:g^{p_i} \equiv i\ (mod\ m) i[1,m1],pi:gpii (mod m)
那么我们可以预处理求出 l o g g i , i ∈ [ 1 , m − 1 ] log_g i,i \in [1,m-1] loggi,i[1,m1],枚举指数就行了。
这里的 l o g log log都是在模意义下的。
现在问题转化:令 I = l o g g i , J = l o g g j , T = l o g g t I=log_g i,J=log_g j,T=log_g t I=loggi,J=loggj,T=loggt,于是有:
G ( x ) = ∑ T = 0 ∞ ∑ I + J ≡ T   ( m o d   φ ( m ) ) ( [ x I ] F ∗ [ x J ] F ) x T G(x)=\sum_{T=0}^{\infty}\sum_{I+J \equiv T\ (mod\ \varphi(m))}^{}{([x^I]F*[x^J]F)}x^T G(x)=T=0I+JT (mod φ(m))([xI]F[xJ]F)xT
于是插入一个数的时候是把它的对数插进去,最后询问的时候询问 l o g g x log_g x loggx的系数即可。
注意取对数之后模数要变为 φ ( m ) \varphi(m) φ(m)(欧拉定理)。
由于是循环卷积,把大等于 φ ( m ) \varphi(m) φ(m)的部分挪到前面去即可。

#include<bits/stdc++.h>
using namespace std;
typedef vector<int> poly;
const int G=3,Log=21,N=1<<Log,mod=1004535809;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline int quickpow(int a,int b,int ret=1){for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
int n,m,X,S,g,Lg[N];
namespace pri_root{
	int tot=0,factor[35];
	bool check(int x){
		for(int i=1;i<=tot;++i){
			int ret=1,tmp=x;
			for(int b=(m-1)/factor[i];b;b>>=1,tmp=tmp*tmp%m)
				if(b&1) ret=ret*tmp%m;
			if(ret==1) return false;
		}return true;
	}
	int find(int P){
		int phi=P-1;
		for(int i=2;i*i<P;++i)
			while(phi%i==0)  factor[++tot]=i,phi/=i;
		if(phi!=1)  factor[++tot]=phi;
		for(int i=1;;++i)
			if(check(i))  return i;
	}
}
using pri_root::find;
namespace POLY{
	int rev[N],*w[Log+1];
	inline void prework(){
		for(int i=1;i<=Log;++i) w[i]=new int[1<<(i-1)];
		int wn=quickpow(G,(mod-1)/(1<<Log));w[Log][0]=1;
		for(int i=1;i<(1<<(Log-1));++i) w[Log][i]=mul(wn,w[Log][i-1]);
		for(int i=Log-1;i;--i)
			for(int j=0;j<(1<<(i-1));++j)
				w[i][j]=w[i+1][j<<1];
	}
	inline void init(int limit){
		for(int i=0;i<limit;++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)*(limit>>1));
	}
	inline void NTT(poly &f,int limit,int type){
		for(int i=0;i<limit;++i) if(rev[i]>i) swap(f[rev[i]],f[i]);
		for(int mid=1,l=1;mid<limit;mid<<=1,++l)
			for(int i=0;i<limit;i+=(mid<<1))
				for(int j=0,p0,p1;j<mid;++j)
					p0=f[i+j],p1=mul(f[i+j+mid],w[l][j]),
					f[i+j]=add(p0,p1),f[i+j+mid]=dec(p0,p1);
		if(type==-1&&(reverse(f.begin()+1,f.begin()+limit),1)){
			int inv=quickpow(limit,mod-2);
			for(int i=0;i<limit;++i) f[i]=mul(f[i],inv);
		}
	}
	inline poly operator*(poly A,poly B){
		int len=A.size()+B.size()-2,limit=1;
		while(limit<=len) limit<<=1;init(limit);
		A.resize(limit),NTT(A,limit,1);
		B.resize(limit),NTT(B,limit,1);
		for(int i=0;i<limit;++i) A[i]=mul(A[i],B[i]);
		NTT(A,limit,-1);
		for(int i=0;i<m-1;++i) A[i]=add(A[i],A[i+m-1]);
		A.resize(m-1);return A;
	}
}
using POLY::operator*;
int main(){
//	freopen("2705.in","r",stdin);
	POLY::prework(),scanf("%d%d%d%d",&n,&m,&X,&S),g=find(m);
	poly A(m,0),ans(m,0);ans[0]=1;
	for(int i=0,p=1;i<m-1;++i,p=p*g%m) Lg[p]=i;
	for(int i=1,x;i<=S;++i){scanf("%d",&x);if(x) A[Lg[x]]=1;}
	for(;n;n>>=1,A=A*A) if(n&1) ans=ans*A;
	printf("%d\n",ans[Lg[X]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值