扩展欧几里得算法
扩展欧几里得算法
前置知识:裴蜀定理
裴蜀定理
若 \(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\).
推导证明
这里的 \(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)\) 成立。
思路
这个问题可以看作求一元线性同余方程的一种特殊情况。
根据裴蜀定理可知,当且仅当 \((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.求解线性同余方程
例题
给定 \(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
思路
这就是上文求逆元方法的扩展和延申。
根据裴蜀定理可知,记 \((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)\) 这一方程:
- 先判断 \((4,5)=1\) 是 \(3\) 的因数,说明有解;
- 再找出满足 \(4x_0+5y_0=1\) 的一组解 \(x_0=-1,\;y_0=1\).
- 最后计算 \(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\).
其中,\(m_1,m_2,\dots m_n\) 两两互质。
注意:这里两两互质是指任意两个数均互质,而不是这些数的最大公因数为1.
例如:{6,8,9} 这三个数的最大公因数为1,但是他们并非两两互质。
解题方法
在此处需要用扩展欧几里得定理计算一个数字的逆元。由此我们就可以知道,这种方法需要 \(m_1,m_2,\dots m_n\) 两两互质的原因就在于需要求每个 \(M_i\) 在模 \(m_i\) 下的逆元(逆元存在的充要条件就是 \(M_i\) 和 \(m_i\) 互质)。
举个例子:
就拿“物不知数”这个经典题举例:
-
首先判断 \(3,5,7\) 之间确实两两互质,可以使用中国剩余定理;
-
求出 \(M=3\times5\times7=105\);
-
分别计算 \(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*} \] -
求解 \(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\) 两两互质这种特殊情况下的构造解,而并不是一元线性同余方程组的通解。
我们可以验证这个定理的正确性:
只需验证
计算 \(\sum\limits_{j=1}^{n}a_jM_jM_j^{-1}\) 的值时,可以看作这个式子中 \(n\) 项分别取模再相加。对于其中某一项:
- 当 \(j\ne i\) 时,其 \(M_j\) 中包含因数 \(m_i\),因此该项对 \(m_i\) 取模得 \(0\).
- 当 \(j= i\) 时,其 \(M_j\cdot M_j^{-1}\) 对 \(m_i\) 取模得 \(1\),故实际为 \(a_i\) 对 \(m_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\) 两两互质,因此不能盲目使用中国剩余定理。
采用另一种通解,步骤如下:
先研究(1)(2)式组成的方程组,可以写作:
变形为:
根据裴蜀定理,要使 \(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_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\),则
中括号表示两个数的最小公倍数。
这样来看,我们把两个式子融合成了一个形式相同的式子:
代码
#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;
}

浙公网安备 33010602011771号