【OI】剩余(入门)

gcd

定义

\(a,b \in \Z\),记 \(a\)\(b\) 的最大公约数为 \(\gcd(a,b)\),最小公倍数为 \(\operatorname{lcm}(a,b)\)

约定 \(\gcd(a,0)=a\)\(\gcd(0,0)=0\)

如何求 \(\gcd(a,b)\)

欧几里得算法

\(d \mid a,b\),则有 \(d \mid a \bmod b\),于是 \(\gcd(a,b)=\gcd(b,a \bmod b)\),称为辗转相除法,可以递归计算。

复杂度为 \(O(\log \max(a,b))\)

证明:

如果一次运算会使得某个数值减半,则它为对数复杂度。

\(a<b\),则 \(\gcd(a,b)=\gcd(b,a)\)

\(a>b\),则 \(a \bmod b\) 至少会让 \(a\) 减半。

得证。

值得注意的是,用欧几里得算法去算斐波那契数列的相邻两项会使复杂度达到最劣。

Stein算法

取模计算速度太慢了,如果换成减法和位运算就会快上很多。

更相减损术:\(\gcd(a,b)=\gcd(b,a-b)\),与辗转相除法同理。

以下为 Stein 算法的过程:

  • \(a \bmod 2=1,b \bmod 2=1\),则 \(\gcd(a,b)=\gcd(b,a-b)\)

  • \(a \bmod 2=1,b \bmod 2=0\),则 \(\gcd(a,b)=\gcd(\frac{a}{2},b)\)\(b\) 也同理。

  • \(a \bmod 2=0,b \bmod 2=0\),则 \(\gcd(a,b)=2 \gcd(\frac{a}{2},\frac{b}{2})\)

除法可以用位运算代替。

同理,根据减半原则,时间复杂度为 \(O(\log \max(a,b))\)

#define ctz __builtin_ctzll
inline int gcd(int a, int b){
	if(!a||!b) return a|b;
	if(a==1||b==1) return 1;
    int az=ctz(a),bz=ctz(b),z=min(az,bz),t; 
	b>>=bz;
    while(a) a>>=az,t=a-b,az=ctz(t),b=min(a,b),a=abs(t);
    return b<<z;
}

值得注意的是,用这个可以过基于值域预处理的gcd这道题,可见效率之高。

exgcd

二元线性丢番图方程:形如 \(ax+by=c\) 的方程,其中 \(a,b,c \in \Z\)

显然,这个方程要么无整数解,要么有无数组整数解。

如何判断这个方程有无整数解?

给出结论:方程有整数解的充要条件是 \(\gcd(a,b) \mid c\)

证明:

必要性:若方程有整数解,则 \(\gcd(a,b) \mid ax\)\(\gcd(a,b) \mid by\),于是 \(\gcd(a,b) \mid ax+by=c\)

充分性
即裴蜀定理,首先考虑方程 \(ax+by=\gcd(a,b)\),需证 \(\exist x,y \in \Z\),使方程成立。

令集合 \(S=\{k \mid ax+by=k,x,y \in \Z\}\),令 \(s\) 为集合的最小正元素,此时 \(ax_0+by_0=s\)

首先 \(\gcd(a,b) \mid s\)

其次令 \(a=ps+q,q=a \bmod s \in [0,s)\),则 \(q=a-ps=a(1-px_0)+b(-py_0)\),于是 \(q \in S\),但是 \(s\) 为最小正元素,所以\(q=0\),可得 \(s \mid \gcd(a,b)\)

综上,\(s=\gcd(a,b)\),此时 \(x=x_0,y=y_0\)

那么如何求得这个方程的解呢?

首先考虑解 \(ax+by=\gcd(a,b)\)

构造两个方程:

\[ax_1+by_1=\gcd(a,b) \]

\[bx_2+(a \bmod b)y_2=\gcd(b,a \bmod b) \]

根据辗转相除法,有:

\[ax_1+by_1=bx_2+(a \bmod b)y_2 \]

注意到 \(a \bmod b=a-\lfloor \frac{a}{b} \rfloor b\)

所以:

\[ax_1+by_1=ay_2+b(x_2-\lfloor \frac{a}{b} \rfloor y_2) \]

\(x_1=y_2,y_1=x_2-\lfloor \frac{a}{b} \rfloor y_2\)

这个可以在辗转相除中递归求解,这样我们就得到了一组特解,然后用通解公式得到所有解。

inline int exgcd(int a, int b, int &x, int &y){
	if(!b){
		x=1,y=0;
		return a;
	}
	int d=exgcd(a,b,y,x);
	y-=a/b*x;
	return d;
}

逆元

对于 \(a,p \in \Z\),称满足 \(ax \equiv 1 \pmod p\)\(x\)\(a\) 在模 \(p\) 意义下的乘法逆元,记为 \(a^{-1}\),只有 \(\gcd(a,p)=1\)\(a \bot p\) 时,\(a\) 才存在逆元。

证明:
\(d= \gcd(a,p)\),则 \(d \mid ax \bmod p\)

费马小定理

\(p \in P\),则 \(a^{p-1} \equiv 1 \pmod p\)

于是 \(a \times a^{p-2} \equiv 1 \pmod p\),即 \(a^{-1} \equiv a^{p-2} \pmod p\)

使用快速幂求,复杂度 \(O(\log p)\)

exgcd

\(p\) 不为素数,无法使用费马小定理。

\[ax \equiv 1 \pmod p \]

\[ax+py=1 \]

然后用 exgcd 求 \(x\),复杂度 \(O(\log a)\)

线性求连续 \(1\) ~ \(n\) 的逆元

首先 \(1^{-1} \equiv 1 \pmod p\)

考虑 \(i^{-1}\),令 \(k = \lfloor \frac{p}{i} \rfloor\)\(j = p \bmod i\),于是 \(ki+j \equiv 0 \pmod p\)

因此 \(kj^{-1}+i^{-1} \equiv 0 \pmod p\),即 \(i^{-1} \equiv -kj^{-1} \pmod p\)

这个 \(j\) 一定小于 \(i\),于是我们可以递推求逆元,复杂度 \(O(n)\)

线性求任意 \(n\) 个数的逆元

给定序列 \(a\),求每个数的逆元。

\(s_i= \prod_{j=1}^i a_i\),先用费马小定理或 exgcd 求 \(s_n^{-1}\),记为 \(sv_n\),然后后缀处理出所有 \(sv_i\)

\(a_i^{-1} \equiv s_{i-1} \times sv_i \pmod p\)

复杂度 \(O(n+\log p)\)

同余方程组

考虑下面这样一个由多个线性同余方程组成的方程组:

\[\begin{cases} x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ ...\\ x \equiv a_n \pmod {m_n} \end{cases} \]

该如何求解?

CRT

考虑 \(\forall m_i\) 两两互素的情况。

  • 中国剩余定理(CRT):令 \(M=\prod m_i,M_i=\frac{M}{m_i}\),有:

\[x \equiv \sum_{i=1}^n a_iM_iM_i^{-1} \pmod M \]

其中 \(M_i^{-1}\) 表示 \(M_i\) 在模 \(m_i\) 下的逆元。

证明:

我们需要证明上面这个式子对每条方程都成立。

考虑一个 \(m_i\),当 \(j \neq i\) 时,\(m_i \mid M_j\),取模后为 \(0\);当 \(j=i\) 时,\(M_iM_i^{-1} \equiv 1 \pmod {m_i}\),则 \(a_iM_iM_i^{-1} \equiv a_i \pmod {m_i}\),于是得证。

但是如果你是个正常人的话,你就会觉得这玩意很蠢。

两两互素,什么雷霆条件?

因此 CRT 在 OI 里一点用都没有,它可以被更通用的 exCRT 平替。

exCRT

接下来考虑 \(m_i\) 不满足两两互素的情况。

先考虑两个方程:

\[\begin{cases} x \equiv a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2} \end{cases} \]

改写成等式:

\[\begin{cases} x = a_1 + Xm_1\\ x = a_2 + Ym_2 \end{cases} \]

\[Xm_1-Ym_2=a_2-a_1 \]

这是一个二元一次丢番图方程,用 exgcd 解,令 \(a=m_1,b=-m_2,c=a_2-a_1,d=\gcd(m_1,m_2)\),求出方程 \(aX+bY=c\) 的特解 \(X_0\)

根据通解公式 \(X=X_0\frac{c}{d}+n \frac{b}{d}\),直接代入第一个等式:

\[x=a_1+X_0 \frac{cm_1}{d}+\frac{nm_1m_2}{d} \]

\[x \equiv a_1+X_0 \frac{cm_1}{d} \pmod {\operatorname{lcm}(m_1,m_2)} \]

这样我们就得到一个新的同余方程,我们成功把两个同余方程合并成一个,于是对于多个同余方程,一直重复合并即可,时间复杂度 \(O(n \log V)\)

板题

#include<bits/stdc++.h>
#define int __int128
#define inf 1e18
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int> 
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=gc();
	while(!isdigit(ch)){
		if(ch=='-')	f=-f;
		ch=gc();
	}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
	return x*f;
}
inline void write(int x){
	if(x<0) pc('-'),x=-x;
	if(x>9)	write(x/10);
	pc(x%10+'0');
}
const int N=114514;
int n,a[N],m[N];
inline int exgcd(int a, int b, int &x, int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,x,y);
	int tmp=y;
	y=x-a/b*y;
	x=tmp;
	return d;
} 
inline int exCRT(){
	int x,y,m1=m[1],a1=a[1];
	int ans=0;
	for(rg int i=2;i<=n;i++){
		int m2=m[i],a2=a[i];
		int A=m1,B=m2,C=a2-a1;
		int d=exgcd(A,B,x,y);
		if(C%d) return -1;
		x=(x*(C/d)+(B/d))%(B/d);
		ans=a1+x*m1;
		m1=m2/d*m1;
		ans=(ans%m1+m1)%m1;
		a1=ans;
	}
	return ans;
}
signed main(){
//	freopen("114514.in","r",stdin);
//	freopen("114514.out","w",stdout);
	n=read();
	for(rg int i=1;i<=n;i++) m[i]=read(),a[i]=read();
	write(exCRT());
	return 0;
}

\[End \]

posted @ 2026-02-08 10:31  bbbzzx  阅读(1)  评论(0)    收藏  举报