欧几里得算法(辗转相除法)与拓展欧几里得算法
更新日志
2024/12/08:开工。2024/12/09:添加通解以及最小正整数解求法与样例代码。
欧几里得算法
用途与原理
欧几里得算法,用于求两个数的最大公约数。
其核心原理为:\(\gcd(a,b)=\gcd(b,a\bmod b)\)
证明
首先,我们证明 \(\gcd(a,b)=\gcd(b,a\bmod b)\)。
为了方便证明,这里我们令 \(a>b\)。
\[\because \gcd(a,b)\mid a \text,\gcd(a,b)\mid b\\ \therefore \gcd(a,b)\mid a-\lfloor \frac{a}{b}\rfloor\cdot b\Leftrightarrow \gcd(a,b)\mid a\bmod b\\ \because \gcd(a,b)\mid b,\gcd(a,b)\mid a\bmod b\\ \therefore \gcd(a,b)\mid \gcd(b,a\bmod b) \]
\[\because \gcd(b,a\bmod b)\mid b,\gcd(b,a\bmod b)\mid a\bmod b\\ \therefore\gcd(b,a\bmod b)\mid a\bmod b+\lfloor \frac{a}{b}\rfloor\cdot b\Leftrightarrow \gcd(b,a\bmod b)\mid a\\ \because \gcd(b,a\bmod b)\mid b,\gcd(b,a\bmod b)\mid a\\ \therefore \gcd(b,a\bmod b)\mid \gcd(a,b) \]
证毕。
当 \(b\mid a\)时,显然此时 \(\gcd(a,b)=b\)。
证毕。
实现细节
- 实际实现中,我们无需判断 \(a>b\),类似于
if(a<b)swap(a,b)
的操作是不必要的。
假如 \(a<b\),那么下一步递归时代入的 \(\gcd(b,a\bmod b)=\gcd(b,a)\),可见没有影响,只多了 \(1\) 的常数。 - 更常见的写法是
if(!b)return a
,当然if(a%b==0)return b
也没有问题,不过你也可以看出前者更简洁(实际没有多少),只是会多出 \(1\) 的常数,但通常情况下%
都是很慢的,所以前者其实并不比后者劣。
模板
int gcd(int a,int b){return (b?gcd(b,a%b):a);}
如果你不喜欢三目运算符或者压行:
int gcd(int a,int b){
if(!b)return a;
return gcd(b,a%b);
}
拓展欧几里得算法
用途
拓展欧几里得算法用来求解形似 \(ax+by=c\) 的线性不定方程。
拓欧常见的利用是求乘法逆元。
原理与证明
首先,我们需要确定上述方程是否有解,根据裴蜀定理,上述方程有解当且仅当 \(\gcd(a,b)\mid c\)。
我们考虑如何借助欧几里得算法求解这个方程。事实上,拓展欧几里得算法是用来求解 \(ax+by=\gcd(a,b)\) 的,但因为 \(\gcd(a,b)\mid c\),所以我们可以以此为跳板求出原方程的解。
思考 \(ax+by=\gcd(a,b)\) 中 \(x,y\) 与 \(bx'+(a\bmod b)y'=\gcd(b,a\bmod b)\) 中 \(x',y'\) 的关系:
这样,我们在求 \(\gcd(a,b)\) 的同时,即可求出 \(x,y\)。
考虑边界情况,当 \(b\mid a\) 时,方程式形如:
显然此时一组可行解为 \(x=0,y=1\),然后回溯即可。
但并没有结束,我们得到的是 \(ax+by=\gcd(a,b)\) 的解,令之为 \(x',y'\),不难发现原方程的解为:
实现细节
- 考虑到 \(\gcd\) 我们会在 \(b=0\) 时退出(如果不是这种写法请忽略),此时应使 \(x=1,y=0\),这样回溯一步就可以使 \(b|a\) 时 \(ax+by=b\) 的解为 \(x=0,y=1\)。
- 上述通过 \(x',y'\) 给 \(x,y\) 赋值的操作有更简洁的写法,可以直接向下传参的时候 \(x'\gets y,y'\gets x\) 且使用引用传参,返回之后 \(y\gets y-\lfloor\frac{a}{b}\rfloor\cdot x\) 即可。
模板
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;
}
常见情景
求乘法逆元
使用 \(\text{exgcd}\) 求乘法逆元的前提是模数与被求的数互质。原因下面会说。
考虑乘法逆元定义:\(ax\equiv 1\pmod p\),其中 \(x\) 即为 \(a\) 逆元。
我们展开原式可得:
那么就可以用拓欧求解了。根据裴蜀定理,有解的前提是 \(\gcd(a,p)\mid 1\),等同于 \(a,p\) 互质。
最后的 \(x\) 无需除以 \(\text{gcd}\) 然后再乘 \(1\),因为这两个都是 \(1\)。
使用格式大概如下:
int x,y;
exgcd(a,p,x,y);
然后 \(x\) 就是 \(a\) 模 \(p\) 意义下的逆元了。
求通解
我们通过拓欧求出的 \(x,y\) 并非是唯一的,它只是一组特解。
考虑找出通解,发现当等式左侧求和的两项分别加减同一个数时结果不变,形如:
那么我们只需要找到所有可行的 \(x+\frac{k}{a}\) 与 \(y-\frac{k}{b}\) 即可。
由于 \(x,y\) 为整数,那么 \(\frac{k}{a}\in Z,\frac{k}{b}\in Z\)。
不难发现 \(k\) 可行的最小步长为 \(\frac{ab}{\gcd(a,b)}\),此时 \(\frac{k}{a}\) 与 \(\frac{k}{b}\) 均为最小步长。
这里的步长指的是最小的变化量使得其整倍数代入后仍是原方程解,且所有解都可以无缺漏被它推出。
那么下面就是这个方程 \(\frac{k}{a}\) 与 \(\frac{k}{b}\) 最小正整数的可行式:
然后我们就可以得到通解了,这里我们令求出的特解为 \(x_0,y_0\):
最小正整数解
我们以 \(x\) 为例。
规定取模运算向零取整(c++
就是这样),\(m\gets\frac{b}{\gcd(a,b)}\)
首先,我们知道, \(x_0\) 加减 \(mk,k\in Z\),仍为原式解。
那么我们需要求的最小正整数解 \(x^*\in(0,m]\)。
那么 \(x_0\bmod m\) 即可得到一组 \(x\in(-m,m)\) 的解。然后我们就可以轻松求出 \(x^*\) 了。
这里令 \(m\) 为正整数,对 \(m<0\) 可以 \(m\gets-1\cdot m\),显然无影响。
实现示例:
int x,y,d,m;
d=exgcd(a,b,x,y);
m=abs(b/d);
int ans=(x%m+m)%m;