【数学】扩展欧几里得算法
二元一次不定方程
扩展欧几里得算法,简称 扩欧 或 \(\rm exgcd\)),是用来求出方程
的整数解的,其中 \(a,b\) 均为整数.
我们考虑欧几里德算法的最后一步,当 \(b=0\) 时,要使得
成立,那么只要取 \(x=1\),\(y\) 取任意整数即可,不妨取 \(y=0\).
因为 \(\gcd(a,b)=\gcd(b,a\bmod b)\),所以可以考虑当整数 \(x,y\) 使得
成立时,如何推出使得
成立的 \(x',y'\).
对于 \(bx+(a\bmod b)y=\gcd(b,a\bmod b)\),
\(\begin{aligned}等式左边&=bx+(a-b\left\lfloor\frac{a}{b}\right\rfloor)y\\&=ay+b(x-\left\lfloor\frac{a}{b}\right\rfloor y)\end{aligned}\)
\(\begin{aligned}等式右边&=\gcd(a,b)\end{aligned}\)
所以要使 \(ax'+by'=\gcd(a,b)\) 的话,取 \(x'=y,y'=x-\left\lfloor\frac{a}{b}\right\rfloor y\) 即可.
就这样一直递归.
下面提供了求解 \(ax+by=\gcd(a,b)\) 的同时返回 \(\gcd(a,b)\) 的代码.
int x, y;
int exgcd(int a, int b)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int Gcd = exgcd(b, a % b);
int tmp = x; // x 会被覆盖掉,先存下来
x = y;
y = tmp - a / b * y;
return Gcd;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
exgcd(a, b);
printf("%d %d\n", x, y);
return 0;
}
现在来考虑如何求出方程
的整数解,其中 \(a,b,c\) 均为整数.
首先,由裴蜀定理知有整数解的充要条件为 \(\gcd(a,b)\mid c\).
当 \(\gcd(a,b)\nmid c\) 时,方程无整数解.
当 \(\gcd(a,b)\mid c\) 时,先求出满足
的整数 \(x',y'\),那么少了 \(\frac{c}{\gcd(a,b)}\) 倍,故令 \(x=x'\cdot\frac{c}{\gcd(a,b)},y=y'\cdot\frac{c}{\gcd(a,b)}\),即可使 \(ax+by=c\).
通解:
假设已经得到了一组解 \(\begin{cases}x=x_1\\y=y_1\end{cases}\),那么方程的通解可表示成 \(\begin{cases}x=x_1+bt\\y=y_1-at\end{cases}\)(\(t\) 为整数)
这个非常显然,手推一下就出来啦。
线性同余方程
求出关于 \(x\) 的同余方程
的最小 正 整数解,其中 \(a,b\) 均为整数.
考虑将原方程转化成
其中 \(y\) 为整数.
题目保证有解,说明这个方程也一定有解,直接用扩欧.
注意要求的是最小 正 整数解,所以得转成整数.
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
int x, y;
void exgcd(int a, int b)
{
if (!b)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b);
int tmp = x;
x = y;
y = tmp - a / b * y;
}
signed main()
{
int a, b;
scanf("%lld%lld", &a, &b);
exgcd(a, b);
printf("%lld\n", (x % b + b) % b);
return 0;
}
练习:P1516 青蛙的约会