同余1:欧几里得算法及其拓展学习笔记
前言:搞定了类欧几里得后,我决定把欧几里得算法给复习一下,顺带复习整个同余相关问题(
欧几里得算法
欧几里得算法没什么可说的,就是求最大公约数,不过有多种方式,这里不再讨论暴力的求法了。
辗转相除法
接下来,直接就到辗转相除法了,辗转相除法用到下面的结论,当其中一个数辗转到零时另一个数就是答案。
结论:
;
证明:设对任意两个不为一的正整数
和
有最大公因数为
,则
可表示为
,
可表示为
,根据最大公因数的定义可知,
和
互质。这里,不妨设
,则
,根据基本常识
,因此
可表示成
,约去
,得
,不难看出括号内的数为整数,则上述结论成立。
通过这个方法,不难看出
也成立。
inline int gcd( int x , int y ) {
return y == 0 ? x : gcd( y , x % y );/*上述讨论建立在x > y的基础上,实际上
这里若x < y,gcd会自动帮我们交换两个数*/
}
时间复杂度为
二进制算法
我以为这样就最优了,实际上我还看到一种“二进制算法”,是在《信息学竞赛之数学一本通》(东南大学出版社)里面看到的,这里也稍微提一下,具体思想就是通过不断去掉因子2来降低常数。过程如下:
1.若
,
均为偶数,则
;
2.若
,
均为奇数,则
;
3.若
,
一奇一偶,不妨设
为偶数(否则将两数置换),则
;
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求出,就是
,证明自己想吧。
在数据非常大以致不得不用高精度的时候,二进制算法的优越性更加明显。
拓展欧几里得算法
拓展欧几里得算法
拓展欧几里得算法可用来求解以下问题,对于已知数(a,b),找出另一组数(p,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;
}
拓展欧几里得算法的应用(解线性同余方程)
拓展欧几里得算法主要用来线性同余方程。
对于方程
, 该方程等价于
,而该方程有整数解的充要条件为
。
对于求解方程,我们先求出
满足
,则方程解可表示为
,这同时也说明了上述充要条件的合理性。
不过,这样求出的只是一组特解,解的一般形式为
其中
为任意正整数。
不过,在做题时,我们一般要求最小正整数解,求法为:
设
,
,y同理。
当
与
互质,
时,求出的
即为
模
意义下的逆元(不懂逆元的可以假装没看到 )。
好了,总体上来说欧几里得算法就没什么了(线性逆元以后讲吧 ),最重要的还是它的应用。这个算法我以前也早已掌握,但在做题时总是依赖题解,证明自己其实并不熟悉它的原理,所谓数论,不是拿来出题的,而是用来快速算出某一步式子的解的,还是要有灵活的思维啊。所以,想要牢固掌握一个知识点,还得多刷题。
板子
同余方程(太裸了 )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;
}