欧几里得算法、扩展欧几里得与同余方程
欧几里得与最大公因数
信竞里最常用的 \(\gcd\) 算法就是欧几里得算法,这是我学会的第一个会写代码但不会算时间复杂度的算法
欧几里得算法的基本定理:对于任意两个数 \(a,b\) ,如果 \(a>b\) ,那么 \(\gcd (a,b) = \gcd(b,a \bmod b)\)
由于这个定理简单易懂,读者自证不难
所以这里显然可以用递归的方法来计算 \(\gcd\)
代码自己去看 最大公因数模板
这里顺便计算一下欧几里得算法的时间复杂度
如果令 \(a,b\leq n\)
根据以上代码,递归分为两种情况:
- \(a<b\),这时候,\(\gcd(a,b)=\gcd(b,a)\)
- \(a \geq b\),这时候,\(\gcd(a,b)=\gcd(b,a \bmod b)\) ,可以得到 \(a \bmod b \leq \dfrac{a}{2}\),所以这种情况最多发生 \(2 \log n\) 次
由于第一情况发生后必然发生第二情况,所以第一情况发生次数必然不大于第二情况发生次数,所以整体递归次数不超过 \(4 \log n\),所以这一算法时间复杂度最坏为 \(O(\log n)\)
扩展欧几里得
这里请出这篇学习笔记的主角——扩展欧几里得算法
扩展欧几里得,根据名字就可以想到,是欧几里得算法的扩展加强版
考虑一道例题:对于三个自然数 \(a,b,c\),求出 \(ax+by=c\) 的整数解
将方程两边同时除以 \(\gcd(a,b)\) :\(\dfrac{a}{\gcd(a,b)}x+\dfrac{b}{\gcd(a,b)}y=\dfrac{c}{\gcd(a,b)}\)
由于 \(\dfrac{a}{\gcd(a,b)}x+\dfrac{b}{\gcd(a,b)}y\) 是一个整数,所以 \(\dfrac{c}{\gcd(a,b)}\) 的值也是整数
即 \(\gcd(a,b)|c\)
所以这个方程要想有整数解,必须满足 \(\gcd(a,b)|c\)
所以先计算 \(\gcd\),然后判断有没有解
考虑这组方程的特殊情况,也就是在 \(c=\gcd(a,b)\) 时,方程变为 \(ax+by=\gcd(a,b)\)
然后用欧几里得基本定理来推一波式子:
于是就得出了扩展欧几里得基本定理:
\(ax+by=\gcd(a,b)\) 与 \(ay+b(x-\lfloor \dfrac{a}{b} \rfloor y)=\gcd(a,b)\) 的整数解一样
void exgcd(int a,int b,int &x,int &y)
{
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
于是乎,扩展欧几里得就完成了,但这里求的是 \(x_0,y_0\),也就是特殊情况
如何求出 \(ax+by=c\) 的解呢?
设 \(k=\dfrac{c}{\gcd(a,b)}\)
将 \(ax_0+by_0=\gcd(a,b)\) 同时乘以 \(k\)
则方程变为 \(akx_0+bky_0=k\gcd(a,b)\)
化简为 \(akx_0+bky_0=c\)
则 \(x=kx_0,y=ky_0\)
则全部代码如下:
void exgcd(int a,int b,int &x,int &y)
{
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
inline int gcd(int a,int b)
{
if(!a) return b;
if(!b) return a;
while(b^=a^=b^=a%=b);
return a;
}
bool answer(int a,int b,int c,int& x,int& y)
{
int d=gcd(a,b);
if(c%d) return 0;
int k=c/d;
exgcd(a,b,x,y);
x*=k,y*=k;
return 1;
}
与欧几里得算法同理,扩展欧几里得的复杂度也是 \(O(\log n)\),
具体证明可以参照欧几里得算法的时间复杂度计算
同余方程
最后的最后,是关于同余方程的部分
是不是很奇怪?为什么会跟同余方程有关系?
将上文中所考虑的 \(ax+by=c\) 的方程两边同时模上 \(b\) ,则整个方程变为 \(ax \equiv c \pmod b\)
就是一个标准的同余方程
于是乎,\(ax+by=c\) 与同余方程是完全等价的
所以用扩展欧几里得就可以求同余方程了
洛谷上用一道模板题,就是[NOIP2012 提高组] 同余方程
不过这道题其实是扩欧求逆元的板子题,只不过算作同余方程的模板也不算错
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
void exgcd(int a,int b,int &x,int &y)
{
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
int inv(int a,int p)
{
int x,y;
exgcd(a,p,x,y);
return (x%p+p)%p;
}
int a,b;
int main()
{
scanf("%d%d",&a,&b);
printf("%d",inv(a,b));
return 0;
}