同余1:欧几里得算法及其拓展学习笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ha_ing/article/details/100048972


前言:搞定了类欧几里得后,我决定把欧几里得算法给复习一下,顺带复习整个同余相关问题( 这是逆生长吗?(手动滑稽)

欧几里得算法

欧几里得算法没什么可说的,就是求最大公约数,不过有多种方式,这里不再讨论暴力的求法了。
辗转相除法
接下来,直接就到辗转相除法了,辗转相除法用到下面的结论,当其中一个数辗转到零时另一个数就是答案。
结论: G C D ( X , Y ) = G C D ( Y , X % Y ) GCD(X,Y) =GCD(Y,X \% Y)
证明:设对任意两个不为一的正整数 x x y y 有最大公因数为 d d ,则 x x 可表示为 t 1 d t_1*d y y 可表示为 t 2 d t_2 * d ,根据最大公因数的定义可知, t 1 t_1 t 2 t_2 互质。这里,不妨设 x > y x > y ,则 t 1 > t 2 t_1 > t_2 ,根据基本常识 a % b = a b a b a \% b = a - b * \lfloor {a \over b} \rfloor ,因此 X % Y X \% Y 可表示成 t 1 d t 2 d t 1 d t 2 d t_1 * d - t_2 * d * \lfloor {t_1 * d \over t_2 * d} \rfloor ,约去 d d ,得 t 1 d t 2 d t 1 t 2 = t 1 t 2 t 1 t 2 d t_1 * d - t_2 * d * \lfloor {t_1\over t_2 } \rfloor = (t_1 - t_2 * \lfloor {t_1 \over t_2} \rfloor)*d ,不难看出括号内的数为整数,则上述结论成立。
通过这个方法,不难看出 G C D ( X , Y ) = G C D ( Y , X Y ) GCD(X,Y) =GCD(Y,X - Y) 也成立。

inline int gcd( int x , int y ) {
	return y == 0 ? x : gcd( y , x % y );/*上述讨论建立在x > y的基础上,实际上
	这里若x < y,gcd会自动帮我们交换两个数*/
} 

时间复杂度为 O ( l o g ( X ) ) O(log(X))

二进制算法
我以为这样就最优了,实际上我还看到一种“二进制算法”,是在《信息学竞赛之数学一本通》(东南大学出版社)里面看到的,这里也稍微提一下,具体思想就是通过不断去掉因子2来降低常数。过程如下:
1.若 x x y y 均为偶数,则 G C D ( x , y ) = 2 G C D x y GCD(x,y) =2*GCD(x,y)
2.若 x x y y 均为奇数,则 G C D ( x , y ) = G C D x y y GCD(x,y) =GCD(x - y,y)
3.若 x x y y 一奇一偶,不妨设 x x 为偶数(否则将两数置换),则 G C D ( x , y ) = G C D x 2 y GCD(x,y) =GCD({ x \over 2},y)
4.当其中一个数为零时返回另一个数。
这个做法的正确性显然,不难得出以下代码:

inline int gcd( int x , int y ) {
	if ( !y ) {
		return x;
	}
	if ( x < y ) {
		x ^= y ^= x ^= y;//整数的快速交换 
	}
	if ( x & 1 && y & 1 ) {
		return gcd( y , x - y );
	} else if ( x & 1 ) {
		return gcd( x , y / 2 );
	} else if ( y & 1 ) {
		return gcd( x / 2 , y );
	} else {
		return 2 * gcd( x / 2 , y / 2 );
	}
}

最小公倍数LCM可利用GCD求出,就是 L C M ( a , b ) = a b G C D ( a , b ) LCM(a,b)={ a * b \over GCD(a,b)} ,证明自己想吧。
在数据非常大以致不得不用高精度的时候,二进制算法的优越性更加明显。

拓展欧几里得算法

拓展欧几里得算法
拓展欧几里得算法可用来求解以下问题,对于已知数(a,b),找出另一组数(p,q),使得满足 a p + b q = G C D ( a , b ) a*p+b*q=GCD(a,b) ,没错,这就是裴蜀定理的内容,我们的目标是证明存在性,如果存在就找到一个合适的算法。
接下来,让我们利用 G C D ( X , Y ) = G C D ( Y , X % Y ) GCD(X,Y) =GCD(Y,X \% Y) 搞些事情:
已知 a p + b q = G C D ( a , b ) a*p+b*q=GCD(a,b) ,通过 G C D ( a , b ) = G C D ( b , a % b ) GCD(a,b) =GCD(b,a \% b)
得出 b p + a % b q = a p + b q b*p&#x27;+a \% b * q&#x27; = a*p+b*q
推出 b p + ( a b a b ) q = a p + b q b*p&#x27;+(a - b * \lfloor {a \over b} \rfloor) * q&#x27; =a*p+b*q
最终得出: a q + b ( p q a b ) = a p + b q a *q&#x27;+b*(p&#x27; - q&#x27;*\lfloor {a \over b} \rfloor) = a*p+b*q
这样就将p,q的变化与a,b的变化挂钩了。
当b = 0时,GCD为a,不难看出p= 1,q = 0,再将(p,q)回溯,经过层层递归回去,即可得到原先(p,q)的值,至此存在性的证明与算法都出来了。

inline int exgcd( int a, int b , int & x , int & y ) {//返回x,y的值即为(p,q) 
	if ( !b ) {
		x = 1;
		y = 0;
		return a;
	}
	int ans = exgcd( b , a % b , x , y );
	int tem = x;
	x = y;
	y = tem - a / b * y;
	return ans;
}

拓展欧几里得算法的应用(解线性同余方程)

拓展欧几里得算法主要用来线性同余方程。
对于方程 a x + b y = c a*x+b*y=c , 该方程等价于 a x c ( m o d b ) a * x\equiv c\pmod b ,而该方程有整数解的充要条件为 c % G C D ( a , b ) = 0 c \% GCD(a,b)=0
对于求解方程,我们先求出 ( x o , y o ) (x_o,y_o) 满足 a x o + b y o = G C D ( a , b ) a*x_o+b*y_o=GCD(a,b) ,则方程解可表示为 a x o c G C D ( a , b ) + b y o c G C D ( a , b ) = G C D ( a , b ) c G C D ( a , b ) a*x_o * {c \over GCD(a,b)}+b*y_o* {c \over GCD(a,b)}=GCD(a,b)* {c \over GCD(a,b)} ,这同时也说明了上述充要条件的合理性。
不过,这样求出的只是一组特解,解的一般形式为 x = x o c G C D ( a , b ) + t b G C D ( a , b ) x = x_o * {c \over GCD(a,b)} +t*{b \over GCD(a,b)} y = y o c G C D ( a , b ) t a G C D ( a , b ) y = y_o * {c \over GCD(a,b)} -t*{a \over GCD(a,b)}
其中 t t 为任意正整数。
不过,在做题时,我们一般要求最小正整数解,求法为:
z = b G C D ( a , b ) z = {b \over GCD(a,b)} x = ( x % z + z ) % z x = (x \% z + z) \% z ,y同理。
a a b b 互质, c = 1 c = 1 时,求出的 x x 即为 a a b b 意义下的逆元(不懂逆元的可以假装没看到 )。
好了,总体上来说欧几里得算法就没什么了(线性逆元以后讲吧 ),最重要的还是它的应用。这个算法我以前也早已掌握,但在做题时总是依赖题解,证明自己其实并不熟悉它的原理,所谓数论,不是拿来出题的,而是用来快速算出某一步式子的解的,还是要有灵活的思维啊。所以,想要牢固掌握一个知识点,还得多刷题。
板子
同余方程(太裸了https://www.luogu.org/problem/P1082
我都不想讲了。

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
ll exgcd( ll a , ll b , ll &x , ll & y ) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	ll d = exgcd( b , a % b , x , y );
	ll t = x;
	x = y;
	y = t - ( a / b ) * y;
	return d;
}
int main () {
	ll a , b , c , x , y;
	cin >> a >> b;
	c = 1;//其实是多余的 
	ll d = exgcd( a , b , x , y );
	x = x * ( c / d );
	y = y * ( c / d );
	ll z = b / d;
	ll k = x;
	k = ( x % z + z ) % z;
	printf("%lld\n",k);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ha_ing/article/details/100048972