一.取模与同余的定义.
取模:定义
a对
b取模后的值
amodb=a−[ba]b,其中
[]表示向
0取整,
b必须为正整数.
同余:定义
a和
b在模
n意义下同余,意为
amodn=bmodn,记为
a≡b(modn).
接下来若无特殊说明,则所有同余式中的未知数都是在模意义下的,即值域是小于模数的自然数.
定理1:
b<a⇒2(amodb)≤b.
分类讨论即可证明.
二.同余的基本性质.
自反性:
a≡a(modn).
对称性:
a≡b(modn)⇒b≡a(modn).
传递性:
a≡b(modn),b≡c(modn)⇒a≡c(modn).
模意义下的加法性质:
a±b≡(amodn)±(bmodn)(modn)a≡b⇔a±c≡b±c(modn)
模意义下的乘法性质:
ab≡(amodn)(bmodn)(modn)a≡b⇒ac≡bc(modn)
但不幸的是,
a≡b(modn)并不是
ac≡bc(modn)的必要条件.
完全剩余系:定义
n的完全剩余系
Sn={x∈N∣x∈[0,n)}.
定理2:
ac≡bc(modn)⇒a≡b(modgcd(n,c)n).
证明:
首先
ac≡bc(modn)⇒n∣(ac−bc)=c(a−b),此时假设
c(a−b)=kn并令
g=gcd(n,c),同时除掉
g,得到:
gc(a−b)=gkn
由于
gcd(gc,gn)=1,必然有
gn∣(a−b),即
a≡b(modgn).
证毕.
这个性质补充了上面乘法满足消去律的条件.
定理2的推论:若
gcd(n,c)=1且
ac≡bc(modn),则有
a≡b(modn).
简化剩余系:定义
n的简化剩余系为
Sn为
n完全剩余系的一个子集,且任意
x∈Sn满足
gcd(n,x)=1.
同时,模意义下加法交换律,加法结合律,乘法交换律,乘法结合律,分配律仍然满足,即:
a+b≡b+a(modn)a+b+c≡a+(b+c)(modn)ab≡ba(modn)abc≡a(bc)(modn)a(b+c)≡ab+ac(modn)
定理3:
n的简化剩余系与模
n意义下的乘法构成一个群.
证明:
考虑一一证明它满足群的性质:
封闭性:若
gcd(n,x)=gcd(n,y)=1,则
gcd(n,xymodn)=gcd(n,xy)=1.
结合律:显然.
单位元:单位元为
1.
可逆性:根据定理2有消去律,以此推出可逆性.
证毕.
三.快速幂与快速乘.
在取模的意义下,我们是有办法用计算机内置的整型变量存储一个幂
ak的值的.于是现在就有了一个问题,如何快速求幂?
把
k按照二进制拆分,我们可以把
ak分解为:
ak=k&2i=0∏a2i
例如
35=3(101)2=322∗320=34∗30=243.
代码如下:
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Power(int a,int k,int p=mod){
int res=1;
for (;k;k>>=1,smul(a,a,p))
if (k&1) smul(res,a,p);
return res;
}
有时候模数过大时,直接乘法会把long long都给爆掉,此时还有一个与快速幂原理类似的快速乘,也就是用加法代替上面的乘法后的快速幂.
代码如下:
LL add(LL a,LL b,LL p=mod){return a+b>=p?a+b-p:a+b;}
LL sadd(LL &a,LL b,LL p=mod){a=add(a,b,p);}
LL mul(LL a,LL k,LL p=mod){
LL res=0;
for (;k;k>>=1,sadd(a,a,p))
if (k&1) sadd(res,a,p);
return res;
}
两个算法的时间复杂度均为
O(logk).
当然,我们知道
amodb=a−⌊ba⌋b,并且C++中还内置了一个非常大的实数类型long double,所以我们还可以写出一个用long double的
O(1)快速乘:
LL mul(LL a,LL b,LL p=mod){
LL t=(long double)a/p*b+1e-8,res=a*b-t*p;
return res<0?res+p:res;
}
四.剩余系与欧拉函数.
定理4:若
gcd(n,m)=1,设
Sn,Sm,Snm分别为
n,m,nm的完全剩余系,则有:
Snm={(an+bm)modnm∣a∈Sm,b∈Sn}
证明:
显然集合
S={(an+bm)modnm∣a∈Sm,b∈Sn}⊆Snm,此时我们只需要证明
∣S∣=∣Snm∣,等价于证明不存在
a,c∈Sm,b,d∈Sn满足
an+bm≡cn+dm(modnm),其中
a=c或
b=d.
假设存在,显然有:
an+bm≡cn+dm(modnm)⇔nm∣[n(a−c)+m(b−d)]⇔m∣(a−c)∧n∣(b−d)⇔a≡c(modm)∧b≡d(modn)
假设不成立.
证毕.
定理5:若
gcd(n,m)=1,设
Sn,Sm,Snm分别为
n,m,nm的简化剩余系,则有:
Snm={(an+bm)modnm∣a∈Sm,b∈Sn}
证明:
而当
gcd(n,m)=1时,假设
a遍取
m的完全剩余系,
b遍取
n的完全剩余系,则有:
gcd(nm,an+bm)=1⇔gcd(n,an+bm)=1∧gcd(m,an+bm)=1⇔gcd(n,bm)=1∧gcd(m,an)=1⇔gcd(n,b)=1∧gcd(m,a)=1
证毕.
定理5的推论:
gcd(n,m)=1⇒ϕ(nm)=ϕ(n)ϕ(m).
根据欧拉函数的积性,我们还可以推导出欧拉函数的计算公式:设
n的唯一分解式
n=∏i=1kpici,则有:
ϕ(n)=ni=1∏kpipi−1
证明:
ϕ(n)=i=1∏kϕ(pici)=i=1∏k(pici−pici−1)=i=1∏kpicipipi−1=ni=1∏kpipi−1
证毕.
五.欧拉定理.
欧拉定理:
gcd(n,a)=1⇒aϕ(n)=1(modn).
证明:
设
n的简化剩余系为
Sn,由于
Sn和模
n意义下的乘法构成一个群,所以有:
x∈Sn⇒axmodn∈Snx,y∈S∧x=y⇒ax≡ay(modn)
那么显然
Sn′={axmodn∣x∈Sn}与
Sn同是
n的简化剩余系.
那么就有:
aϕ(n)x∈Sn∏x≡x∈Sn∏ax≡x∈Sn∏x(modn)
可以同时去掉
∏x∈Snx,得到
aϕ(n)≡1(modn).
证毕.
费马小定理:若
n为素数且
a不是
n的倍数,则有
an−1≡1(modn).
欧拉定理的推论:当
gcd(a,n)=1时,
ak≡akmodϕ(n)(modn).
扩展欧拉定理:若
k≥ϕ(n),则
ak≡akmodϕ(n)+ϕ(n)(modn).
证明:
为了证明这个定理,我们先证明
a为素数时定理成立:
设
n=arn′,其中
gcd(a,n′)=1,此时有:
aϕ(n′)≡1(modn′)
由于
ϕ(n′)∣ϕ(n),有:
aϕ(n)≡1(modn′)
同时让两边和模数乘上
ar,得到:
aϕ(n)+r≡ar(modn)
于是有:
ak+r≡akmodϕ(n)+r(modn)
显然有
k≥ϕ(n)=(a−1)ar−1ϕ(n′)≥r,所以有:
ak≡ar+k−r≡ar+ϕ(n)+(k−r)modϕ(n)≡armodϕ(n)+(k−r)modϕ(n)+ϕ(n)≡akmodr+ϕ(n)(modn)
至此我们证明了
a为素数的情况.
那么对于素数的幂
a=pc,有:
pk≡pkmodϕ(n)+ϕ(n)(modn)⇒(pk)c≡(pkmodϕ(n)+ϕ(n))c(modn)⇒ak≡akmodϕ(n)+ϕ(n)(modn)
对于任意一个数
a,直接写出它的唯一分解形式,会得到一些下面的式子:
(pici)≡(pici)kmodϕ(n)+ϕ(n)(modn)
把左右两边分别相乘即可得到原定理.
证毕.
六.线性同余方程求解.
同余方程:在模意义下求解的方程称为同余方程.
一般来说,我们求模
n意义下同余方程的解都是在
[0,n)范围内的整数.
线性同余方程:形如
∑i=1maixi≡c(modn)的同余方程称为线性同余方程.
关于线性同余方程的求解,通常是将其转化为如下形式:
i=1∑maixi−nx=c
然后就可以套线性丢番图方程的求解方法了.
与一般线性丢番图方程求解不同的是,这个问题是可以用一些技巧做到不爆long long的.
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
int t[N+9];
bool Get_root(int *a,int n,int c,int p=mod,int *res){
a[++n]=p;
int g=a[1];
res[1]=t[n]=1;
for (int i=2;i<=n;++i) g=Ex_gcd(g,a[i],t[i-1],res[i]);
if (c%g) return 0;
c/=g;
for (int i=n-1;i>=1;--i){
smul(t[i]+=(t[i]%=p)<0?p:0,t[i+1],p);
smul(res[i]+=(res[i]%=p)<0?p:0,t[i],p);
}
for (int i=1;i<=n;++i) smul(res[i],c,p);
return 1;
}
七.模意义下的乘法逆元.
逆元:一般数论中的逆元是指在模
n意义下的乘法逆元,即对于一个整数
a,逆元是指一个满足
ax≡1(modn)的整数
x,通常情况下要求
x是
[1,n)内的整数.
通常来说,若模数
n没有特殊性质,则求逆元可以转化为线性同余方程来解决,即转化为:
ax≡1(modn)⇔ax−ny=1
同时,根据裴蜀定理,我们也能知道在
gcd(n,a)=1时,
a在模
n意义下不存在逆元.
逆元的唯一性:在模
n意义下一个数
a只有最多一个逆元.
证明:
显然逆元定义转化后得到的式子通解为:
x=x0+kn
它们在模
n意义下必然同余.
证毕.
在
n为素数的时候,一个优美的性质是,所有
[1,n)上的整数都存在模
n意义下的逆元.
可以用费马小定理来求解,或者套用模数为素数时逆元的递推公式.
逆元递推公式:
i−1≡−⌊in⌋(nmodi)−1(modn).
证明:
n≡0(modn)⌊in⌋i+(nmodi)≡0(modn)nmodi≡−⌊in⌋i(modn)i−1≡−⌊in⌋(nmodi)−1(modn)
证毕.
用这个递推公式可以在
O(n)的时间复杂度内求出
[1,n]在模一个质数意义下的逆元.
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int n,inv[N+9];
void Get_inv(int p=mod){
inv[1]=1;
for (int i=2;i<=n;++i) inv[i]=mul(p-p/i,inv[p%i],p);
}
同时,我们还可以直接用公式递归求解
n的逆元.根据定理1得到这个做法的时间复杂度为
O(logn).
代码如下:
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
int Get_inv(int n,int p=mod){return mul(p-p/n,Get_inv(p%n,p),p);}
美中不足的是,这个做法不能
O(n)预处理任意
n个数
x1,x2,x3,⋯xn的逆元.但是我们有另一个离线求逆元的简单方法.
具体就是分三步走:
x−1=(i=1∏nxi)−1modpmuli≡(j=1∏ixj)−1≡xi+1muli+1(modp)xi−1≡mulij=1∏i−1xj(modp)
若
p为素数,可以在
O(logp)的时间复杂度内求出
x−1modp.
时间复杂度
O(n+logp).
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Power(int a,int k,int p=mod){int res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}
int Get_inv(int a,int p=mod){return Power(a,p-2,p);}
int n,a[N+9],inv[N+9];
void Get_inv(int p=mod){
inv[n]=1;
for (int i=1;i<=n;++i) smul(inv[n],a[i],p);
inv[n]=Get_inv(inv[n],p);
for (int i=n-1;i>=1;--i) inv[i]=mul(inv[i+1],a[i+1],p);
int t=1;
for (int i=1;i<=n;++i) smul(inv[i],t,p),smul(t,a[i],p);
}
八.线性同余方程组与中国剩余定理.
线性同余方程组:由线性同余方程组成的方程组称为线性同余方程组.
这里我们主要解决如下的线性同余方程组:
⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2)⋮x≡an(modmn)
中国剩余定理:对于上面的线性同余方程组,若
mi两两互素,则存在一个解
x满足:
Mi=mi∏j=1nmj,Mixi≡1(modmi)⇒x=i=1∑naixiMi
证明:
若
i=j,必然有
Mi≡0(modmj),否则必然有:
xiMi≡1(modmi)⇒aixiMi≡ai(modmi)
显然只要
xiMi≡1(modmi)存在解,这个定理就可以用了.
此时根据
mi两两互质,有
gcd(mi,Mi)=1,这个方程必然有解.
证毕.
这个东西求解线性同余方程组的时间复杂度为
O(nlogm).
代码如下:
int add(int a,int b,int p){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p){return (LL)a*b%p;}
void sadd(int &a,int b,int p){a=add(a,b,p);}
void ssub(int &a,int b,int p){a=sub(a,b,p);}
void smul(int &a,int b,int p){a=mul(a,b,p);}
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
int Get_inv(int a,int p){int res,y,g=Ex_gcd(a,p,res,y);return g^1?-1:(res%=p)<0?res+p:res;}
int CRT(int *a,int *m,int n){
int res=0,mod=1;
for (int i=1;i<=n;++i) mod*=m[i];
for (int i=1;i<=n;++i)
sadd(res,mul(a[i],mul(Get_inv(mod/m[i],m[i]),mod/m[i],mod),mod),mod);
return res;
}
从上面的过程中可以看出,当
mi不满足两两互质的时候,中国剩余定理就无法求解了.
那么怎么求解呢?我们可以考虑通过前
i−1个方程的解求出前
i个方程的解.
具体来说,若我们有了前
i−1个方程的解
x,设
m=lcmj=1i−1{mi},那么前
i−1个方程的通解为
x+km,此时我们把问题变为如何求
x+km≡ai(modm).
容易发现这是一个线性同余方程,直接用exgcd求解.
时间复杂度
O(nlogm).
代码如下:
int add(int a,int b,int p){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p){return (LL)a*b%p;}
void sadd(int &a,int b,int p){a=add(a,b,p);}
void ssub(int &a,int b,int p){a=sub(a,b,p);}
void smul(int &a,int b,int p){a=mul(a,b,p);}
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
int CRT(int *a,int *mo,int n){
int res=a[1],mod=mo[1];
for (int i=2;i<=n;++i){
int x,y,c=sub(a[i],res%mo[i],mo[i]),g=Ex_gcd(mod,mo[i],x,y);
if (c%g) return -1;
int t=mo[i]/g;
x=mul((x%=t)<0?x+t:x,c/g,t)*mod;
mod*=mo[i]/g;sadd(res,x,mod);
}
return res;
}
九.威尔逊定理.
二次探测定理:对于一个素数
n,若有
x满足
x2≡1(modn),则必然有
x=1或
x=n−1.
证明:
首先
n=2时定理显然成立.
x2≡1(modn)⇔n∣(x2−1)⇔n∣(x+1)(x−1)⇔n∣(x+1)∨n∣(x−1)
由于
p为素数,所以只能取
x=1或
x=p−1.
证毕.
威尔逊定理:
n是素数
⇒(n−1)!≡−1(modn).
证明:
显然定理等价于
n是素数
⇒(n−2)!≡1(modn).
令
S={2,3,4,⋯,n−2},此时根据二次探测定理,对于任意
x∈S,不可能有
x2≡1(modn).
同时根据逆元的唯一性,就可以把
S中的元素两两配对使得
xy≡1(modn),也就是说:
i=2∏n−2i≡(n−2)!≡1(modn)
证毕.
威尔逊定理逆定理:
(n−1)!≡−1(modn)⇒n是素数.
证明:
首先我们知道
n为素数时
(n−1)!≡−1(modn),接下来只需要证明
n不为素数时不满足
(n−1)!≡−1(modn)即可.
若
n不为素数,必然存在两个整数
a,b∈(1,n)满足
ab=n,即
ab≡0(modn),分两类讨论:
1.若
a=b,则
(n−1)!中必然含有
ab,
(n−1)!≡0(modn).
2.若
a=b,除
n=4时
(n−1)!≡2(modn)外,必然有
a>2,此时
(n−1)!必然含有
a(2a),即
(n−1)!≡0(modn).
证毕.
十.组合数取模与Lucas定理.
关于组合数取模,首先有一个小定理.
定理6:
n为素数时,若
m不为
0或
n,则有
(mn)≡0(modn).
证明:
暴力提出
n:
(mn)≡m!(n−m)!n!≡nm!(n−m)!(n−1)!≡0(modn)
证毕.
至于为什么
m=0或
m=n时不行,那是因为这个时候在模意义下除
0了.
我们再来看一个被称为组合数取模的问题:给定
n,m,p,求
(mn)modp,其中
n,m很大但
p比较小.
Lucas定理:若
p为素数,则有
(mn)=(⌊pm⌋⌊pn⌋)(mmodpnmodp)(modp).
证明:
写出组合数的生成函数:
i=0∑n(in)xi=(1+x)n
根据费马小定理有:
(1+x)p≡1+x(modp)
根据上式可以推出:
(1+x)n≡(1+x)⌊pn⌋p(1+x)nmodp≡(1+x)⌊pn⌋(1+x)nmodp(modp)
再次展开可以得到 :
i=0∑n(in)xi≡i=0∑⌊pn⌋(i⌊pn⌋)xii=0∑nmodp(inmodp)xi≡i=0∑⌊pn⌋(i⌊pn⌋)xipi=0∑nmodp(inmodp)xi(modp)
显然
xm的系数与
x⌊pm⌋和
xmmodp的系数之积同余.
证毕.
那么在
p为素数的时候,我们可以先在
O(p)的时间复杂度内预处理出阶乘与阶乘逆,之后就可以在
O(logn)的时间复杂度内回答
(mn)modp了.
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int inv[N+9],fac[N+9],ifac[N+9];
void Get_inv(int p=mod){
inv[1]=1;
fac[0]=fac[1]=1;
ifac[0]=ifac[1]=1;
for (int i=2;i<p;++i){
inv[i]=mul(p-p/i,inv[p%i],p);
fac[i]=mul(fac[i-1],i,p);
ifac[i]=mul(ifac[i-1],inv[i],p);
}
}
int Get_c(int n,int m,int p=mod){return n<m?0:mul(fac[n],mul(ifac[m],ifac[n-m],p),p);}
int Lucas(int n,int m,int p=mod){
if (n<m) return 0;
int res=1;
for (;n&&m;n/=p,m/=p) smul(res,Get_c(n%p,m%p,p),p);
return res;
}
但是
p不为素数时该怎么办呢?
显然,把
p唯一分解后
p=∏i=0kpici,若
ci=1,直接对于每个素因子
pi求
(mn)modpi,最后CRT合并即可.
但是
ci不为
1,即模数为素数幂
pici的情况呢?
显然有
(mn)=m!(n−m)!n!,我们考虑分别计算三个阶乘后通过逆元求解.
但是,逆元只有在模数与需要求逆的数互质时才能求,所以对于阶乘中的数,我们需要分两部分来考虑,一部分是
pi的倍数,另一部分不是
pi的倍数.
显然对于不是
pi倍数的数,我们注意到
a≡a+pici(modpici).此时设
f(n)表示
[1,n]上的不是
pi倍数的整数乘积,则有:
f(n)≡f(pici)⌊picin⌋f(nmodpici)(modpici)
这个式子可以
O(pci)求解.
那么对于
pi的倍数呢?观察一下它们的形式为:
pi,2pi,3pi,⋯
显然我们可以把
pi先提出来,然后剩下的数变为:
1,2,3,⋯
突然发现这个东西等价于求
⌊pn⌋!,就是原来的问题,可以递归计算,
pi外的数的贡献解决.
然后对于
pi,很容易计算出
pi的数量,从而避免求逆,计算
pi贡献的问题就解决了.
时间复杂度
O(p
+∑picilogn),可以粗略的估计为
O(plogp).
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Power(int a,int k,int p=mod){int res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
int Get_inv(int a,int p=mod){int res,y,g=Ex_gcd(a,p,res,y);return g^1?-1:(res%=p)<0?res+p:res;}
int d[C+9],c[C+9],cd;
void Get_d(int n){
int now=n;
for (int i=2;i*i<=n;++i)
if (now%i==0)
for (d[++cd]=i;now%i==0;now/=i) ++c[cd];
if (now>1) d[++cd]=now,c[cd]=1;
}
int Get_fact(int n,int p,int pi){
if (!n) return 1;
int res=1,t=n%p;
for (int i=1;i<p;++i)
if (i%pi) smul(res,i,p);
res=Power(res,n/p,p);
for (int i=1;i<=t;++i)
if (i%pi) smul(res,i,p);
return mul(res,Get_fact(n/pi,p,pi),p);
}
int Get_c(int n,int m,int p,int pi){
if (n<m) return 0;
int res=mul(Get_fact(n,p,pi),Get_inv(mul(Get_fact(m,p,pi),Get_fact(n-m,p,pi),p),p),p),cnt=0;
for (int i=n/pi;i;i/=pi) cnt+=i;
for (int i=m/pi;i;i/=pi) cnt-=i;
for (int i=(n-m)/pi;i;i/=pi) cnt-=i;
return mul(res,Power(pi,cnt,p),p);
}
int a[C+9],mo[C+9];
int CRT(){
int res=a[1],mod=mo[1];
for (int i=2;i<=cd;++i){
int x,y,c=sub(a[i],res%mo[i],mo[i]),g=Ex_gcd(mod,mo[i],x,y);
if (c%g) return -1;
int t=mo[i]/g;
x=mul((x%=t)<0?x+t:x,c/g,t)*mod;
mod*=mo[i]/g;sadd(res,x,mod);
}
return res;
}
int Lucas(int n,int m,int p=mod){
if (n<m) return 0;
Get_d(p);
for (int i=1;i<=cd;++i){
mo[i]=1;
for (int j=1;j<=c[i];++j) mo[i]*=d[i];
a[i]=Get_c(n,m,mo[i],d[i]);
}
return CRT();
}
十一.高次同余方程引入.
高次同余方程:次数不为
1的同余方程,在这里主要指以下两类同余方程:
ax≡c(modn)xa≡c(modn)
其中第一类方程对应了在模意义下取对数,即求
logacmodn,这个问题对应了离散对数;第二类方程对应了在模意义下开高次根,即求
ac
modn,这个问题对应了高次剩余.
接下来将会详细讨论这两块内容.
十二.离散对数问题与BSGS算法.
离散对数问题:离散对数问题指的是求解第一类高次同余方程,即:
ax≡c(modn)
接下来我们讲解如何求解.
把
x写成
x=ki−j的形式,得到:
aki−j≡c(modn)
先假设有
gcd(n,a)=1,则根据上面定理2的推论,此时有:
aki≡caj(modn)⇒aki−j≡c(modn)
这个时候我们就可以把问题转化为第一个式子了.而对于第一个问题,先把所有
cajmodn插入一个容器中,然后枚举
i,找到在容器中第一个满足
akimodn
i,并得到对应的
j,那么答案就是
ki−j.
显然取
t=n
最优.
若用map实现这个容器时间复杂度为
O(n
logn),用hash实现为
O(n
).
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
map<int,int>mp;
int BSGS(int a,int c,int p=mod){
if (c==1) return 0;
int siz=sqrt(p)+1,pw=1;
for (int i=0;i<siz;++i) mp[mul(pw,c,p)]=i,smul(pw,a,p);
a=pw;pw=1;
for (int i=1;i<=siz;++i){
smul(pw,a,p);
if (mp.find(pw)==mp.end()) continue;
return i*siz-mp[pw];
}
return -1;
}
然而在
gcd(n,a)=1的时候,在模意义下的乘法不满足消去律了,这个时候就不能套上面的BSGS算法了,怎么办?
考虑把式子转化成
gcd(n,a)=1的形式.
首先排除
a=0和
x=0的情况,排除之后我们有
a∈[1,n),x>0.
此时必然有
a∣ax,可以把
ax写成
ax=ax′的形式,发现一个同余方程,根据裴蜀定理有解时必然有
gcd(n,a)∣c.
然后我们考虑有解时,设
g=gcd(n,a),根据定理2有:
gaax−1≡gc(modgn)⇒ax≡c(modn)
这样我们就消掉了一部分公因数,但只消一次是不够的,应该一直消到
a与模数的gcd为
1为止.
时间复杂度仍为
O(n
logn)或
O(n
).
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
map<int,int>mp;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int BSGS(int a,int c,int p=mod){
if (!a) return !c?1:-1;
if (c==1) return 0;
int res=0,t=1;
for (int g=gcd(a,p);g^1;g=gcd(a,p)){
if (c%g) return -1;
c/=g;p/=g;
smul(t,a/g,p);++res;
if (t==c) return res;
}
int siz=sqrt(p)+1,pw=1;
for (int i=0;i<siz;++i) mp[mul(pw,c,p)]=i,smul(pw,a,p);
a=pw;pw=t;
for (int i=1;i<=siz;++i){
smul(pw,a,p);
if (mp.find(pw)==mp.end()) continue;
return res+i*siz-mp[pw];
}
return -1;
}
十三.阶相关.
阶:最小的满足
ax≡1(modn)的正整数
x被称为
a在模
n意义下的阶,记为
δna.
阶的性质1:存在
δna的充分必要条件为
gcd(n,a)=1,且存在时必然有
δna∣ϕ(n).
证明:
第一部分:
gcd(n,a)=1⇒∃δna.
可以用欧拉定理证明.
第二部分:
∃δna⇒gcd(n,a)=1.
原命题等价于证明存在
k满足
ak≡1(modn).
由于
k为正整数,所以
a∣ak,设
ak=ax,那么有:
ak≡1(modn)⇔ax−ny=1
根据裴蜀定理,有解的条件为
gcd(n,a)=1.
第三部分:
δna∣ϕ(n).
设
ϕ(n)=kδna+r,其中
0<r<δna,那么有:
ar≡akδna+r≡1(modn)
与阶的定义矛盾,原命题得证.
证毕.
阶的性质2:
ax≡1(modn)⇒δna∣x.
证明与阶的性质1第三部分类似.
阶的性质3:若
gcd(n,a)=1,则
{aimodn∣0≤i<δna}⊆n的简化剩余系.
证明:
设
S为
n的简化剩余系且
a∈S,那么
aimodn∈S,问题转化为证明
i=j⇒ai≡aj(modn),其中
i,j∈[1,n)且为整数.
不妨设
i>j,此时假设
ai≡aj(modn),根据定理2有:
gcd(n,a)=1∧ai≡aj(modn)⇒ai−j≡1(modn)
显然此时必须有
i=j,否则
δna就应该为
i−j了.
证毕.
阶的性质4:
δn(ak)=gcd(δna,k)δna
从群论的角度很容易证明.
现在我们来学习一下如何求解阶.
首先根据阶的性质1,我们只需要能在
gcd(n,a)=1的时候求
δna就好了,并且
δna∣ϕ(n).
那么我们只需要枚举一下
ϕ(n)的约数并用快速幂判定即可.
时间复杂度
O(n
+n31logn).
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Power(int a,int k,int p=mod){int res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int Get_phi(int n){
int res=n,now=n;
for (int i=2;i*i<=n;++i)
if (now%i==0)
for (res=res/i*(i-1);now%i==0;now/=i);
if (now>1) res=res/now*(now-1);
return res;
}
int Get_delta(int a,int p=mod){
if (gcd(a,p)^1) return -1;
int phi=Get_phi(p),res=phi;
for (int i=1;i*i<=phi;++i)
if (phi%i==0){
if (Power(a,i,p)==1) res=min(res,i);
if (i*i^phi&&Power(a,phi/i,p)==1) res=min(res,phi/i);
}
return res;
}
十四.原根相关.
原根:定义
g为
n的原根,当且仅当
δng=ϕ(n).
原根的性质1:若
g为
n的原根,则
{gimodn∣0≤i<ϕ(n)}是
n的简化剩余系,并且
gimodn两两不同.
根据阶的性质3即可证明.
原根的性质2:若
g为
n的原根,则
gkmodn是
n的原根当且仅当
gcd(ϕ(n),i)=1.
根据阶的性质4即可证明.
原根的性质3:若一个数
n存在原根,则它的原根的数量为
ϕ(ϕ(n)).
证明:
若
n存在一个原根
g,则
{gimodn∣0≤i<ϕ(n)}都满足
giϕ(n)≡1(modn).
根据原根的性质2,
gimodn是
n的原根需要满足
gcd(ϕ(n),i)=1,那么所有满足条件的数的数量为:
i=0∑ϕ(n)−1[gcd(ϕ(n),i)=1]=ϕ(ϕ(n))
证毕.
原根的存在性:设
p为任意奇素数,则只有
2,4,pk,2pk存在原根,其中
k是某个正整数.
接下来我们来求原根.不过在这之前,我们需要先引入一个性质.
原根的性质4:设唯一分解
n=∏i=1kpici,则
g为
n的原根当且仅当
gcd(n,g)=1且对于任意
i满足
gpin≡1(modn).
很显然不证明了.
根据原根的性质4,不难写出一个时间复杂度为
O(n
+glog2n)求原根算法,其中
g为最小原根.
貌似这已经是目前最快的求解原根算法了,一般
g都挺小的所以这个算法并没有非常慢.
代码如下:
int add(int a,int b,int p=mod){return a+b>=p?a+b-p:a+b;}
int sub(int a,int b,int p=mod){return a-b<0?a-b+p:a-b;}
int mul(int a,int b,int p=mod){return (LL)a*b%p;}
void sadd(int &a,int b,int p=mod){a=add(a,b,p);}
void ssub(int &a,int b,int p=mod){a=sub(a,b,p);}
void smul(int &a,int b,int p=mod){a=mul(a,b,p);}
int Power(int a,int k,int p=mod){int res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}
int Get_phi(int n){
int res=n,now=n;
for (int i=2;i*i<=n;++i)
if (now%i==0)
for (res=res/i*(i-1);now%i==0;now/=i);
if (now>1) res=res/now*(now-1);
return res;
}
int d[C+9],c[C+9],cd;
void Get_d(int n){
cd=0;
int now=n;
for (int i=2;i*i<=n;++i)
if (now%i==0)
for (d[++cd]=i,c[cd]=0;now%i==0;now/=i) ++c[cd];
if (now>1) d[++cd]=now,c[cd]=1;
}
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int Get_proot(int p=mod){
Get_d(p);
if (cd==1&&d[1]==2&&c[1]>2||cd==2&&(d[1]^2||c[1]>1)||cd>2) return -1;
int phi=Get_phi(p);
Get_d(phi);
for (int res=1;res<p;++res)
if (gcd(p,res)==1){
int flag=1;
for (int i=1;i<=cd;++i)
if (Power(res,phi/d[i],p)==1) {flag=0;break;}
if (!flag) continue;
return res;
}
}
十五.关于原根存在性的证明.
咕.
十六.离散对数.
离散对数:设
g为
n的原根,则称
gx≡c(modn)的解
x为
c在模
n意义下以
g为底的离散对数,记为
indgc(modn).
离散对数可以通过BSGS算法求解,并且根据原根的性质1,在
gcd(n,c)=1时离散对数必然有解.
定理7:代数上的log运算与离散对数性质基本相同,即:
indg(ab)≡indga+indgb(modn)indgan≡nindga(modn)
离散对数还可以求解第二类高次同余方程,即:
xa≡c(modn)
对两边取离散对数,得到:
aindgx≡indgc(modn)indgx≡a−1indgc(modn)x≡ga−1indgc(modn)
十七.二次剩余与Cipolla算法.
二次剩余方程:定义高次同余方程
x2≡c(modn)为二次剩余方程,其中
n为奇素数.
二次剩余:称
c为
n的二次剩余,当且仅当二次剩余方程
x2≡c(modn)存在解.
我们先来看一看如何判定一个数是否为二次剩余.
勒让德记号:定义勒让德记号
(nc)为:
(nc)=⎩⎨⎧01−1n∣c∃x∤n,x2≡c(modn)∄x∤n,x2≡c(modn)
欧拉准则:
(nc)=c2n−1modn.
证明:
对于
n∣c的情况显然正确.
若存在
x∤n满足
x2≡c(modn),由于
n为素数,根据费马小定理有:
c2n−1≡xn−1≡1(modn)
否则根据费马小定理,一定有:
cn−1≡1(modn)
根据二次探测定理,此时有:
c2n−1≡1(modn)∨c2n−1≡−1(modn)
把
c写成一个
n的原根
g的幂的形式,即
c≡g2k+1(modn),此时有:
c2n−1≡gk(n−1)+2n−1≡g2n−1(modn)
根据原根的定义,能够使得
gx≡1(modn)的
x必然不小于
ϕ(n)=n−1,所以必然有:
c2n−1≡−1(modn)
证毕.
有了欧拉准则,我们就可以在
O(logn)的时间内判定一个数是否是模
n意义下的二次剩余了.
欧拉准则的推论:
(na)(nb)≡(nab)
接下来我们开始求二次剩余方程的一个解.
定理8:设
ω=(a2−c)modn,其中
a是一个
[1,n)上的整数,若有
(nω)=−1,则
(a+ω
)2n+1是
x2≡c(modn)的一个整数解.
证明:
先来证明一下
(a+ω
)2n+1是一个解,即
(a+ω
)n+1≡c(modn).
考虑将
(a+ω
)n二项式展开得到:
(a+ω
)n=i=0∑n(in)an−iω2i
根据定理6有:
(a+ω
)n≡an+ω2n(modn)
根据费马小定理与欧拉准则有:
(a+ω
)n≡a−ω
(modn)
所以最后有:
(a+ω
)n+1≡(a+ω
)(a−ω
)≡a2−ω≡c(modn)
然后我们来证明一下
(a+ω
)2n+1是个整数.
假设
(a+ω
)2n+1≡xa+yω
(modn),那么此时有:
(xa+yω
)2≡x2a2+2xyaω
+y2ω≡c(modn)
此时必然有
转载:
https://blog.csdn.net/hzk_cpp/article/details/86773602