同余 与 数论其他

一.定理

费马小定理&欧拉定理

  • p 为质数且 a0(modp),则 ap11(modp).

  • gcd(a,m)=1,则 aφ(m)1(modm).

  • (扩展欧拉定理)若 bφ(p),则有 ababmodφ(p)+φ(p)(modp).

二.算法

欧几里得算法

给定 a,b,求 gcd(a,b)

我们发现,有:

gcd(a,b)={ab=0gcd(b,amodb)b0

利用这个东西递归计算即可。

扩展欧几里得(exgcd)

  • g=gcd(a,b),求解下列方程:ax+by=g

设计函数 exgcd(ll a,ll b,ll &x,ll &y)

b=0 时,我们解此时的方程 a0x+b0y=g=a0,只需令 x=1,y=0 即可。

若已经知道了 bx+(amodb)y=g 的一组解 x0,y0,如何借出方程 ax+by=g 的一组解?

bx0+(amodb)y0=bx0+(aabb)y0=bx0+ay0abby0=a(y0)+b(x0aby0)

于是我们就得到了一组解 x=y0,y=x0aby0,不停地向上回溯即可解出原方程的解。

代码:

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){ x=1,y=0;return a; }
	ll d=exgcd(b,a%b,x,y);
	ll z=x; x=y; y=z-y*(a/b);//妈妈生的!!a/b要带括号!!! 
	return d;
}

注:

方程 ax+by=c 当且仅当 gcd(a,b)c 时有解。

线性同余方程

求解同余方程 axb(modm)

只需解出 axmy=b 的一组解即可,运用 exgcd 即可。

当且仅当 gcd(a,m)b 时有解。

//解同余方程 ax≡b(mod m)
ll TYFC(ll a,ll b,ll m){
	ll x,y,mm; 
	ll G=exgcd(a,m,x,y);
	if(b%G) return -1;
	x*=b/G;mm=m/G;
	return (x%mm+mm)%mm;
}

类欧几里得算法,万能欧几里得算法与 SB 树

单独写一篇博客了,在这里

乘法逆元

同余方程 ax1(modm) 的解 x=a1 记作 am 的乘法逆元

m 为奇质数

此时由费马小定理 ama(modm),两边同时除以 a2am2a1(modm),于是直接快速幂就好。

如果要 O(n) 求出 1n 得所有数逆元,可以处理处阶乘和阶乘逆元。

m 不为质数

要求 am 互质时 a1 才存在。

可以试做方程 ax+my1(modm) 这个方程 x 的一个解。

于是使用 exgcd 解方程就行。

中国剩余定理

参考:蓝书、oi-wiki

用来求解以下方程组:

{xa1(modm1)xa2(modm2)xan(modmn)

其中满足 mi,两两互质。

我们记 m=i=1nmiMi=mmi;记 tiMimi 下的逆元,即为 Miti1(modmi) 的一个解。

于是我们有 x 的整数解:

x0=i=1naiMiti

证明也很简单,考虑方程 x0ak(modmk),对于所有的 ik,显然 Mi=mmimk 的倍数,故 aiMiti0(modmi)

而对于 akMktk 项来说,因为 Mktk1(modmk),故 akMktkak(modmk),方程组成立。

此时 x 的通解为 x=km+x0

扩展中国剩余定理

上述情况下如果不保证 mi 两两互质。

蓝书是这么做的:

假设求出了 xk1 满足前 k1 个方程,记 Mk1=lcmi=1k1mi,则前 k1 个方程的通解为 x=xk1+tMk1

考虑第 k 个方程,相当于 xk1+tMk1ak(modmk)。方程等价于 Mk1takxk1(modmk),其中只有 t 是变量。

解这个方程得到 t,于是有了前 k 个方程的解 xk=xk1+tMk1

如此来 n 遍就求出了所有方程的解。其中任何一步如果不满足 gcd(Mk1,mk)(akxk1) 则无解。

阶与原根

参照这篇博客

一.定义

由欧拉定理,对于整数 a 和 正整数 m,若有 gcd(a,m)=1,则:

aφ(m)1(modm)

也就是说,满足 an1(modm) 的最小正整数 n 一定存在,这个 n 称作 a m 的阶,记作 δm(a)

如果有 δm(a)=φ(m),则称 am 的原根

二.性质

δm(a)φ(m)

证明咕了。

当且仅当 m=1,2,4,pn,2pn 时,m 有原根。

证明咕了。

m 有原根,则 m 共有 φ(φ(m)) 个原根。

证明咕了。

m3,gcd(g,m)=1,则当且仅当对于 φ(m) 的每一个素因子 p,有:

gφ(m)p1(modm)

时,g 为模 m 的原根。

离线对数问题

求解方程 axb(modp)

BSGS算法

此部分来源于算法竞赛进阶指南。

全称为 BabyStepGiantStep,适用于 gcd(a,p)=1

为何必须限定 gcd(a,p)=1?因为只有在满足 a,p 互质时,模 p 意义同余方程两边才能同时进行关于 a 的加法、乘法运算。

由欧拉定理,axφ(p) 次就会出现循环节,故解一定小于 p

设解为 x=itj,其中 t=p,0j<t,则有:

aitjb(modp)

即:

aitbaj(modp)

我们枚举 j[0,t1],将 bajmodp 插入一个 map 中。

然后我们枚举 i[0,t] ,计算 aitmodp,在 map 中查找是否存在对应的 j,更新答案即可。

时间复杂度 O(p)

点击查看代码
ll BSGS(ll a,ll b,ll p){
	if(a%p==0){
		if(b%p==1&&a!=0) return 0;
		else if(b%p==0) return 1;
		else return -1;
	}
	map <ll,int> mp; mp.clear();
	b%=p;ll t=ceil(sqrt((double)p)),now=b;
	for(int j=0;j<t;j++){
		if(j) (now*=a)%=mod;
		mp[now]=j;
	}
	now=1;a=qpow(a,t,p);
	for(int i=0;i<=t;i++){
		if(i) (now*=a)%=mod;
		if(mp.find(now)!=mp.end()&&i*t-mp[now] >=0) return i*t-mp[now];  
	}return -1;
}

扩展BSGS算法

还是求解这个方程,但这里没有 gcd(a,p)=1

我们做如下处理:

同余式不好处理,我们令:

ax+np=b

t=gcd(a,p)

tb,无解,除非 b=1 时解为 x=0

t=1,直接 BSGS 就行了。

其他情况下,我们让式子左右同时除以 t

axt+npt=bt

即:

atax1bt(modpt)

由于此时 atpt 互质,故 at 在模 pt 意义下有逆元。由于 pt 不是质数,费马小定理不适用,我们用 exgcd 求出逆元,于是有:

ax1bt(at)1(modpt)

发现这个问题和原问题一模一样,于是递归求解。

递归层数不超过 logn 级别。

点击查看代码
ll TYFC(ll a,ll m){
	ll x,y; exgcd(a,m,x,y);
	return (x%m+m)%m;
}
ll exBSGS(ll a,ll b,ll p){
	b%=p; if(b==1||p==1) return 0;
	ll t=gcd(a,p);
	if(b%t) return -INF;
	if(t==1){
		return BSGS(a,b,p);
	}
	ll inv=TYFC(a/t,p/t);
	return exBSGS(a,b/t*inv,p/t)+1;
}

高次剩余

咕咕咕咕咕咕咕咕咕

二次剩余

单独写了博客

其他

斐波那契相关

单独写了博客

O(1)gcd

不是,这玩意太邪门了。

我们记值域为 mt=m,于是可以 O(m) 预处理,O(1)gcd

参考了这三篇博客:OneInDarkweixin_30414635
hhoppitree

证明去翻这几篇博客吧,我反正是通篇不写证明,有空再写。

一.引理

对于任意正整数 xm,均能够把 x 分为 x=abc,其中 a,b,c 这三个数,要么 <m,要么是质数。

二.算法流程

我们预处理这些东西:

  1. m 以内的 gcd 表,复杂度为 O(mm)=O(m)

代码:

for(int i=1;i<=t;i++){
	gcd[i][0]=gcd[0][i]=i;
	for(int j=1;j<=t;j++){
		gcd[i][j]=gcd[j%i][i];
	}
}
  1. m 以内正整数的分解。

对于正整数 x,假设它的最小质因数为 vxv 分解为 abc,则我们对其中最小的一个乘以 v,即可得到 x 的分解。

代码:

split[1][0]=split[1][1]=split[1][2]=1;
for(int i=2;i<=maxm-10;i++){
	ll minn=INF,mini;
	for(int j=0;j<3;j++){
		split[i][j]=split[i/v[i]][j];
		if(split[i][j] < minn) minn=split[i][j],mini=j;
	}
	split[i][mini] *= v[i];
}

接下来我们尝试实现 gcd(x,y)

首先,对于 x,ym,直接返回算好的 gcd 表。

我们将 x 分解为 abc(下述中记为 x0x1x2),然后分别用每个数和 y 计算 gcd,再合并答案。

具体的,每局 i[0,3),对于 x 的一个分解 xi,若 xim,则我们直接计算 d=gcd(xi,ymodxi);否则,xi 一定是质数,则如果 ymodxi=0,取 d=xi,否则取 d=1

然后我们让 ansansdyyd,即可。

代码:

ll Gcd(ll x,ll y){
	if(x<=1010 && y<=1010) return ggcd[x][y];
	ll res=1,tmp;
	for(ll i=0;i<3;i++){
		if(split[x][i]==1) continue;
		if(split[x][i] <= 1010) tmp=ggcd[split[x][i]][y%split[x][i]];
		else if(y%split[x][i]==0) tmp=split[x][i];
		else tmp=1;
		res *= tmp;y/=tmp;
	}return res;
}
posted @   一匹大宝羊  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示