目录
引言
什么是扩展欧几里得,听起来好高深,别急先从欧几里得下手;
欧几里得算法(gcd)
- 欧几里得算法的用处:求两个数的最大公约数;
- 原理:辗转相除法;
辗转相除法:用a除以b(这里是a>b,当然,在程序编程中,求两个数的最大公约数,可以不限a和b的大小,a<b也就是多一次循环)得到结果q和余数r,再用除数b除以余数r 再得到一个余数,再用除数除以余数,…如此循环,直到余数为0,那么此时的除数就是最大公因数
辗转相除法能够成立基于以下定理:
- Theorem:gcd(a,b)= gcd(b,a%b)
要想证明这个定理,需要知道两个引理:
- 若d是a和b的公约数,那么d也是b和c的公约数(c为a%b)
- 若d是b和c的公约数(c为a%b),那么d也是a和b的公约数
这两个引理的是什么意思呢,拿第一个来说;
假设一个数d是a的因子,也就是a=m*d,同时也是b的因子,b=n*d,那么a%b = a-w*b = (m-w*n)*d;
由此可得,a的因子集合、b的因子集合和c的因子集合是相同的;
然后利用上述定理当余数为0的时候,除数就是最大公约数;
可是为什么会出现余数为0的情况呢,为什么这个时候除数是最大公约数?????
按照辗转相除法的方法如此循环下去,
因为每次的余数r肯定小于除数n
那么相当于每次的被除数(变为上次的除数)变小,
除数(变为上次的余数)也变小,而公因数的集合一直不变
也就是被除数和除数越来越小,而二者所包含的公共公因数集合又不
变,这样下去,除数总有一次会变为最大的公因数。
代码模板:
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
或者使用c++函数 __gcd();
扩展欧几里得(exgcd)
顾名思义extend—gcd,就是在欧几里得算法的过程基础上进行修改;
扩展欧几里得用途:
- 判断方程ax+by=m是否有解
- 求ax+by=m的任意一组解、通解、最小整数解
- 求逆元
首先除了了解过欧几里得算法,还要知道裴属定理:
- ax+by=gcd(a , b)
即如果a、b是整数,那么一定存在整数x、y使得ax+by=gcd(a,b)。
换句话说,如果ax+by=m有解,那么m一定是gcd(a,b)的若干倍。(可以来判断一个这样的式子有没有解)
难道仅仅局限于知道是否有解么?当然好奇的人类是想知道在有解的情况下,这个解是多少,也就是x和y的值是多少;
扩展欧几里得就能实现(根据模板讲解吧):
代码模板:
// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1; y = 0;
return a; //到达递归边界开始向上一层返回
}
int d = exgcd(b, a % b, x, y);
int temp=y; //推出这一层的x,y
y=x-(a/b)*y;
x=temp;
return d;
}
首先当递归到达边界时,余数b==0,除数a就是最大公约数,此时可以得出来方程的一组解
x=1,y=0; ——> a*1+b*0=gcd(a,b);
注意在递归中永远都是先得到上一层的结果状态
假设栈中当前层得到的解是x1,y1;那么 就要求下一层的结果状态了;代入方程得:
b*x1 + (a%b)*y1 = gcd(a,b)
—》b*x1+(a-(a/b)*b)*y1 = gcd(a,b)
—》a*y1+ b*(x1-(a/b)*y1) = gcd(a,b)
显而易见由上一层的解 x1,y1,可以推出下一层x,y的状态
x=y1 y= x1-(a/b)*y1
于是相邻的两个层之间的状态关系就出来了,ey~
由上述模板可以求出方程 ax+by=gcd(a,b) 的任意一组解x0,y0;那么方程ax+by=k的通解的形式是什么样的呢?
( ax+by=k 中的k一定是gcd(a,b)的倍数,方程才会有解)
具体推导:扩展欧几里得求通解、最小正整数解
因为由 ax+by=gcd(a,b) 变成 ax+by=k 方程扩大了 k/gcd(a,b)倍,所以通过扩欧求出来的x0,y0也扩大了相同的倍数
x0=x0* (k/gcd(a,b)) y0=y0* (k/gcd(a,b))
通解形式为:
- x=x0+b/gcd(a,b) * n (相当于x每次可以增减:b/gcd的整数倍)
- y=y0+a/gcd(a,b) * n (相当于y每次可以增减:a/gcd的整数倍) 《注意:x求出来后,y通常由x代入方程求得》
最小正整数解:
- x=(x+b/gcd*n)%(b/gcd) = x%(b/gcd) (b/gcd(a,b)应取正)
- 若x<=0,则x+=b/gcd