洛谷 P1082 [NOIP2012 提高组] 同余方程
刷一刷数论的题,这才知道有扩展欧几里得算法这个东西(扩欧)。虽然还不知道欧几里得算法是什么,就一步步来看。
欧几里得算法,在《整除与剩余》板块找到了。
求两个数a,b的最大公约数。(哦豁,原来欧几里得算法就是求最大公约数的算法,幻想的太高大上了)。
接口:
int gcd(int a,int b);
复杂度 O(logN),N和a,b同阶
输入:a,b 两个整数
输出:a,b的最大公约数
代码:
int gcd(int a,int b){
return b == 0? a :gcd(b, a % b);
}
从欧几里得算法还引出一个叫做贝祖定理的东西,看起来就像欧几里得算法的前身,不过通过计算机更容易实现a%b的操作。贝祖定理是这样说的:
gcd(a,b) = gcd(b,a-b),其中a≥b。如此不断迭代,直到b=0,a就是原来数对的最大公约数。把a减b减到不能减为止,在计算机上直接表示为 a mod b就好。
看完贝祖定理的描述,想起了开始学习C语言时使用的辗转相除法:
代码:
//余数rem,数对a, b
int gcd(int a,int b){
while(b>0){
rem = a % b;
a = b;
b = rem;
}
return a;
}
从辗转相除和贝祖定理,到欧几里得算法,形式变得越来越简单,性能变得越来越强大(竖大拇指)。
记小本本了:
//欧几里得算法:求a,b的最大公约数
int gcd(int a,int b){
return b == 0? a :gcd(b, a % b);
}
写这么多,还没说题呢。
此题要使用扩展欧几里得算法,???好像,好像还没有说扩展欧几里得算法。。。继续说叭。
扩展欧几里得算法
用于求A,B的最大公约数,且求出X,Y满足AX+BY=GCD(A,B)
看得出来,多了点东西,嗯,有意思:
AX+BY=GCD(A,B)
让我们看看这个东西怎么理解。
刚刚说过求最大公约数gcd:
int gcd(int a,int b){
return b == 0? a :gcd(b, a % b);
}
我们知道,A和A是两个非负整数,每次调用函数A用B替换,B用A%B替换,有两种情况。
第一种情况,B=0了,B==0,return返回值就是A了。
左边成了AX,右边GCD(A,B)就是A。
显而易见,X=1,Y=0(此时Y是任何数都没有影响了,因为B是0,0乘任何数都是0,代码中,当B=0时,更改Y的值结果不变)
第二种情况,B>0,我们已经讲了GCD(A, B) = GCD(B,A mod B)
A可以替换为B,B可以替换为A%B,然后把前面也替换一下看看。
BX’ + (A mod B)Y’ = GCD(B, A mod B) = GCD(A, B)
其中X’是变换后的原来X位置的值,Y’是变换后的原来Y位置的值。
结合一点点数学知识,和C语言的向下取整,把A mod B 变一下形式。
A%B = (A - A/B × B)Y’ ,展开:
AY’ + BX’ - (A/B) × BY’ = GCD(A, B),提取一下B:
AY’ + B(X’ - A/B × Y’) = GCD(A, B)
与原式子对比一下:AX+BY=GCD(A,B)
能得出来:
AY’ + B(X’ - A/B × Y’) = AX+BY,成!
X = Y’, Y = X’ - A/B × Y’,爽!
接下来代码造出来:
void exgcd(int a,int b){
if(b==0){
x=1;
y=0;
return;
}
exgcd(b, a%b);
k = x;
x = y;
y = k - x*(a/b);
return;
}
接口:
void exgcd(int a,int b);
复杂度:O(logN), N与a,b同阶。
输入:a,b 两个整数。
输出:x就为满足条件 ax ≡ 1(mod b)的a的乘数。
如此,扩展欧几里得算法:
继续记小本本:
//扩展欧几里得算法
void exgcd(int a,int b){
if(b==0){
x=1;
y=0;
return;
}
exgcd(b, a%b);
k = x;
x = y;
y = k - x*(a/b);
return;
}
诶!ax ≡ 1(mod b)这是个啥。继续下去。
这个柿子(式子)的意思是a,b已知,求x,满足(a*x)%b = 1
x 有个自己的名字 乘法逆元。
(终于离题目近了一点)
继续套娃:乘法逆元又是个啥:
离散数学上有概念:存在群G,中有元素a,e为单位元。
满足 a’ × a = a × a’ = e,a’为逆元。
(其中单位元e是:a×e = a)在实数中任何数乘1得他本身,所以1是单位元。)
看题吧,x的同余方程 ax≡ 1 (mod b),求最小整数解。
这不是求乘法逆元吗?
安排:
#include<iostream>
typedef long long ll;
using namespace std;
ll x ,y;
//扩展欧几里得算法求乘法逆元
void exgcd(ll a,ll b)
{
if(b==0)
{
x=1;
y=0;
return;
}
exgcd(b,a%b);
ll tx = x;
x = y;
y = tx - a/b * y;
}
int main()
{
ll a,b;
cin >> a >> b;
exgcd(a,b);
x = (x%b+b)%b;
cout << x;
return 0;
}
end!