传送门
参考博客
其实模数是一个很重要的东西。当你看到
998244353
998244353
998244353或
1004535809
1004535809
1004535809时,不管怎么说,都要用生成函数试着思考一下,不然对不起这个模数啊。
998244353
=
2
119
∗
23
+
1
,
g
=
3
998244353=2^{119}*23+1,g=3
998244353=2119∗23+1,g=3
1004535809
=
2
479
∗
21
+
1
,
g
=
3
1004535809=2^{479}*21+1,g=3
1004535809=2479∗21+1,g=3
顺便复习一下,单位根可以做
N
T
T
NTT
NTT,是因为一个性质:
对于
P
=
2
n
∗
k
+
1
P=2^n*k+1
P=2n∗k+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=kP−1。
这样就可以满足:
g
t
0
,
g
t
1
,
⋯
,
g
t
t
−
1
g_t^0,g_t^1,\cdots,g_t^{t-1}
gt0,gt1,⋯,gtt−1互不相同且满足原根性质。参见此文
总之,遇见一个模数不是
1
e
9
+
7
1e9+7
1e9+7的题,就看有没有
m
o
d
−
1
=
2
k
∗
p
mod-1=2^k*p
mod−1=2k∗p这个性质。
当然,更重要的是对生成函数的理解。。
我们写出长度为1的序列的生成函数:
F
(
x
)
=
∑
i
=
0
∞
[
i
∈
S
]
x
i
F(x)=\sum_{i=0}^{\infty}[i\in S]x^i
F(x)=i=0∑∞[i∈S]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=0∑∞i∗j≡t (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,m−1],∃pi:gpi≡i (mod m)
那么我们可以预处理求出
l
o
g
g
i
,
i
∈
[
1
,
m
−
1
]
log_g i,i \in [1,m-1]
loggi,i∈[1,m−1],枚举指数就行了。
这里的
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=0∑∞I+J≡T (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]]);
}
本文深入探讨了模数998244353和1004535809在算法竞赛中的应用,特别是在生成函数和离散对数领域的关键作用。文章详细解释了如何利用这些特殊模数的特性进行快速傅立叶变换(FFT),并介绍了通过取对数将乘法转化为加法的技巧,以解决复杂序列问题。
953

被折叠的 条评论
为什么被折叠?



