扩展欧几里得算法

扩展欧几里得算法

前置知识:裴蜀定理

裴蜀定理

\(a,b\) 是整数,且 \((a,b)=d\),那么对于任意的整数 \(x,y,ax+by\) 都一定是 \(d\) 的倍数。特别地,一定存在整数 \(x,y\),使 \(ax+by=d\) 成立,(x,y不唯一)。

\(x_0,y_0\) 满足 \(ax+by=d\),则满足 \(\begin{cases}x=x_0-\frac{b}{d}\cdot k \\ y=y_0+\frac{a}{d}\cdot k\end{cases},k\in \mathbb Z\),的 \(x\)\(y\) 也均满足上式。且这些是 \(x,y\) 所有可能的解。

作用

扩展欧几里得算法就是用来求解这个特殊的 \(x,y\).

推导证明

\[\begin{gather*} a\;mod\;b=a-\lfloor \frac{a}{b} \rfloor \cdot b \\ d=(a,b)=(b,a\;mod\;b)=\left(a-\lfloor \frac{a}{b} \rfloor \cdot b\right)x+by \\ d=ax+b\left(y-\lfloor \frac{a}{b} \rfloor x\right) \end{gather*} \]

这里的 \(y-\lfloor \frac{a}{b} \rfloor x\) 就是下面代码第9行 y -= a / b * x; 的由来。

代码

#include <iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y) {		//以传址的方式返回两个参数
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int a, b, x, y;
	scanf("%d%d", &a, &b);
	exgcd(a, b, x, y);
	printf("%d %d\n", x, y);
	return 0;
}

应用

1.求逆元

前置知识:逆元

若整数 \(b\)\(m\) 互质,并且对于任意的整数 \(a\),如果满足 \(b|a\),则存在一个整数 \(x\),使得 \(\frac{a}{b}\equiv a\times x(mod\space m)\),则称 \(x\)\(b\) 的模 \(m\) 乘法逆元,记为 \(b^{−1}(mod\space m)\)

也就是说,求得一个 \(x\),使得 \(b\cdot x\equiv 1(mod\space m)\) 成立。

思路

这个问题可以看作求一元线性同余方程的一种特殊情况。

\[\begin{gather*} \exists x\in \mathbb{Z},使得\;ax\equiv 1(mod \; m)\;成立 \\ \Updownarrow \\ \exists x,y\in \mathbb{Z},使得\;ax=my+1 \;成立 \\ \Updownarrow \\ 令y'=-y,\; \exists x,y'\in \mathbb{Z},使得\;ax+my'=1 \;成立 \end{gather*} \]

根据裴蜀定理可知,当且仅当 \((a,m)=1\),即 \(a\)\(m\) 互质时,此线性同余方程有解,即 \(a\) 有存在逆元。

利用扩展欧几里得算法求得的 \(x\) 即为 \(a\) 的逆元。

代码

#include <iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y) {		//以传址的方式返回两个参数
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int a, m, x, y;
	scanf("%d%d", &a, &m);
	int d = exgcd(a, m, x, y);
	if (d == 1)
		printf("%d\n", x < 0 ? x + m : x);		//为了防止出现负数,等价于:printf("%d\n", (x + m) % m);
	else
		puts("no exist");
	return 0;
}

2.求解线性同余方程

例题

878. 线性同余方程 - AcWing题库

给定 \(n\) 组数据 \(a_i,b_i,m_i\),对于每组数求出一个 \(x\) ,使其满足 \(a_i*x_i\equiv b_i\pmod m_i\),如果无解,则输出 impossible.

数据范围

\(1 \le n \le 10^5,\quad1\le a_i,b_i,m_i\le 2\times 10^9\)

输入样例

2
2 3 6
4 3 5

输出样例

impossible
2

思路

这就是上文求逆元方法的扩展和延申。

\[\begin{gather*} \exists x\in \mathbb{Z},使得\;ax\equiv b(mod \; m)\;成立 \\ \Updownarrow \\ \exists x,y\in \mathbb{Z},使得\;ax=my+b \;成立 \\ \Updownarrow \\ 令y'=-y,\; \exists x,y'\in \mathbb{Z},使得\;ax+my'=b \;成立 \end{gather*} \]

根据裴蜀定理可知,记 \((a,m)=d\) 当且仅当 \(d\;|\;b\) 时,此线性同余方程有解。

要求 \(x\) ,则需用扩展欧几里得算法算出满足 \(ax_0+my_0=d\) 式子的 \(x_0\),可得 \(x=x_0\cdot\frac{b}{d}\) .

举个例子:

要解 \(4x\equiv 3(mod\;5)\) 这一方程:

  1. 先判断 \((4,5)=1\)\(3\) 的因数,说明有解;
  2. 再找出满足 \(4x_0+5y_0=1\) 的一组解 \(x_0=-1,\;y_0=1\).
  3. 最后计算 \(x\)\(-1\times\frac{3}{1}=-3\),因此解为 \(x\equiv-3(mod\;5)\).

代码

#include <iostream>
using namespace std;
typedef long long LL;
int exgcd(int a, int b, int &x, int &y) {
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int n;
	scanf("%d", &n);
	while (n--) {
		int a, b, m, x, y;
		scanf("%d%d%d", &a, &b, &m);
		int d = exgcd(a, m, x, y);
		if (b % d)
			puts("impossible");
		else{
            LL res = (LL)x * b / d % m;
            printf("%lld\n", res < 0 ? res + m : res);
		}
	}
	return 0;
}

3.求解线性同余方程组

前置知识:中国剩余定理

中国剩余定理又称孙子定理或中国余数定理,是数论中的一个关于一元线性同余方程组的定理,说明了一元线性同余方程组有解的准则以及求解方法。

经典题目表述

有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

大意是:一个整数除以三余二,除以五余三,除以七余二,求这个整数。

现代数学的语言来说明,题目为:给出以下一元线性同余方程组,求满足下列同余式的整数 \(x\).

\[\begin{cases} x\equiv a_1(mod\;m_1) \\ x\equiv a_2(mod\;m_2) \\ \vdots \\ x\equiv a_n(mod\;m_n) \\ \end{cases} \]

其中,\(m_1,m_2,\dots m_n\) 两两互质

注意:这里两两互质是指任意两个数均互质,而不是这些数的最大公因数为1.

例如:{6,8,9} 这三个数的最大公因数为1,但是他们并非两两互质。

解题方法

\[\begin{gather*} 设M=\prod\limits_{i=1}^{n}m_i,\quad M_i=\frac{M}{m_i} \\ 用M_i^{-1}表示M_i在模m_i意义下的逆元,则解为: \\ x\equiv \sum\limits_{i=1}^{n}a_iM_iM_i^{-1}\;(mod\;M) \end{gather*} \]

在此处需要用扩展欧几里得定理计算一个数字的逆元。由此我们就可以知道,这种方法需要 \(m_1,m_2,\dots m_n\) 两两互质的原因就在于需要求每个 \(M_i\) 在模 \(m_i\) 下的逆元(逆元存在的充要条件就是 \(M_i\)\(m_i\) 互质)。

举个例子:

\[\begin{cases} x\equiv 2(mod\;3) \\ x\equiv 3(mod\;5) \\ x\equiv 2(mod\;7) \\ \end{cases} \]

就拿“物不知数”这个经典题举例:

  1. 首先判断 \(3,5,7\) 之间确实两两互质,可以使用中国剩余定理;

  2. 求出 \(M=3\times5\times7=105\)

  3. 分别计算 \(M_i\)\(M_i^{-1}\)

    \[\begin{gather*} M_1=5\times7=35,\;M_1^{-1}=2^{-1}(mod\;3)=2; \\ M_2=3\times7=21,\;M_2^{-1}=1^{-1}(mod\;5)=1; \\ M_3=3\times5=15,\;M_3^{-1}=1^{-1}(mod\;7)=1; \\ \end{gather*} \]

  4. 求解 \(x\).

    \[\begin{align*} x&=a_1M_1M_1^{-1}+a_2M_2M_2^{-1}+a_3M_3M_3^{-1} \\ &=2\times35\times2+3\times21\times1+2\times15\times1 \\ &=233\\ 因此,x&\equiv 23 (mod\;105) \end{align*} \]

    最小的正整数解为 \(23\)

注意:

这个定理给出了在 \(m_1,m_2,\dots m_n\) 两两互质这种特殊情况下的构造解,而并不是一元线性同余方程组的通解。

我们可以验证这个定理的正确性

只需验证

\[对于\forall i \in \{x\in \mathbb{N}^*|1\le x \le n\},均满足\sum\limits_{j=1}^{n}a_jM_jM_j^{-1} \equiv a_i(mod\;m_i) \]

计算 \(\sum\limits_{j=1}^{n}a_jM_jM_j^{-1}\) 的值时,可以看作这个式子中 \(n\) 项分别取模再相加。对于其中某一项:

  1. \(j\ne i\) 时,其 \(M_j\) 中包含因数 \(m_i\),因此该项对 \(m_i\) 取模得 \(0\).
  2. \(j= i\) 时,其 \(M_j\cdot M_j^{-1}\)\(m_i\) 取模得 \(1\),故实际为 \(a_i\)\(m_i\) 取模。

综上所述,

\[\sum\limits_{j=1}^{n}a_jM_jM_j^{-1}(mod\;m_i)=a_i+0+0+\cdots+0=a_i \]

代码:
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y) {		//扩展欧几里得算法
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	LL d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
LL M = 1, m1[30], m2[30];
LL a[30], m[30], res;
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld%lld", a + i, m + i);
		M *= m[i];
	}
	for (int i = 1; i <= n; i++) {
		m1[i] = M / m[i];
		LL x, y;
		LL d = exgcd(m1[i], m[i], x, y);
		if (d == 1) {
			m2[i] = x < 0 ? x + m[i] : x;
		} else {				//说明不满足两两互质
			puts("error!");
			return 0;
		}
		res += a[i] * m1[i] * m2[i] % M;
	}
	res %= M;
	printf("%lld", res < 0 ? res + M : res);
	return 0;
}

例题

204. 表达整数的奇怪方式 - AcWing题库(略微改动)

给定 \(2n\) 个整数 \(a_1,a_2,\dots,a_n\)\(m_1,m_2,\dots,m_n\),求一个最小的非负整数 \(x\),满足 \(\forall i \in [1,n],x\equiv a_i(mod\;m_i)\).

数据范围

\(1\le a_i\le2^{31}-1,\)

\(0\le a_i <m_i\)

\(1\le n\le 25\)

输入样例

2
7 8			//a1和m1
9 11		//a2和m2

输出样例

31

思路

此题没有指明 \(a_1,a_2,\dots a_n\) 两两互质,因此不能盲目使用中国剩余定理

采用另一种通解,步骤如下:

\[\begin{cases} x\equiv a_1(mod\;m_1)\qquad(1) \\ x\equiv a_2(mod\;m_2)\qquad(2) \\ \vdots \\ x\equiv a_n(mod\;m_n) \\ \end{cases} \]

先研究(1)(2)式组成的方程组,可以写作:

\[\begin{gather*} x=k_1m_1+a_1\;(k_1\in\Bbb{Z}) \\ \quad=k_2m_2+a_2\;(k_2\in\Bbb{Z}) \end{gather*} \]

变形为:

\[k_1m_1-k_2m_2=a_2-a_1\qquad\qquad(3) \]

根据裴蜀定理,要使 \(k_1,k_2\) 有解,即 \((m_1,m_2)|(a_2-a_1)\).

\(d=(m_1,m_2)\) ,利用扩展欧几里得算法可以得到 \(k_1m_1-k_2m_2=d\) 的一组解,记作 \(k_1',k_2'\)。将其扩大至成为(3)式的解:

\[\begin{cases} k_{10}=k_1'\cdot \frac{a_2-a_1}{d} \\ k_{20}=k_2'\cdot \frac{a_2-a_1}{d} \end{cases} \]

那么所有的解为 \(\begin{cases}k_1=k_{10}+\frac{m_2}{d}\cdot p \\ k_2=k_{20}+\frac{m_1}{d}\cdot p\end{cases},p\in \mathbb Z\),则

\[\begin{align*} x&=k_1m_1+a_1\qquad\qquad\qquad\; \\ &=\left(k_{10}+\frac{m_2}{d}\cdot p\right)m_1+a_1\\ &=m_1k_{10}+a_1+p\cdot\frac{m_1m_2}{d} \\ &=m_1k_{10}+a_1+p\cdot[m_1,m_2] \end{align*} \]

中括号表示两个数的最小公倍数。

这样来看,我们把两个式子融合成了一个形式相同的式子:

\[\begin{align*} 令M&=[m_1,m_2],\;A=m_1k_{10}+a_1 \\ 则: x&=p\cdot M+A\\ 即: x&\equiv A(mod\;M) \end{align*} \]

代码

#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y) {		//扩展欧几里得算法
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	LL d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int n;
	scanf("%d", &n);

	bool flag = true;		//是否有解
	LL a1, m1;

	scanf("%lld%lld", &a1, &m1);

	for (int i = 1; i < n; i++) {
		LL a2, m2;
		scanf("%lld%lld", &a2, &m2);

		LL k1, k2;
		LL d = exgcd(m1, m2, k1, k2);
		if ((a2 - a1) % d) {
			flag = false;		//无解
			break;
		}

		k1 *= (a2 - a1) / d;
		LL t = m2 / d;
		k1 = (k1 % t + t) % t;		//更新k1为符合要求的最小正整数

		a1 = m1 * k1 + a1;			//转化成一个新的式子
		m1 = m1 / d * m2;
	}
	if (flag)
		printf("%lld\n", (a1 % m1 + m1) % m1);
	else
		puts("-1");
	return 0;
}
posted @ 2025-04-11 21:04  H_Elden  阅读(55)  评论(0)    收藏  举报