拓展欧几里得算法详解
拓展欧几里得算法详解
贝祖定理
即如果a、b是整数,那么一定存在整数x、y使得ax + by = gcd(a,b)。
换句话说,如果ax+by=m有解,那么m一定是gcd(a,b)的若干倍。(可以来判断一个这样的式子有没有解)
为了求解最大公约数,要用到辗转相除法,代码实现如下:
int gcd(int a,int b){
return b == 0 ? a:gcd(b,a%b);
}
扩展欧几里得
由于贝祖定理最多只能用于判断ax + by = gcd(a,b)是否有解,而不能确定这个解释多少,所以要引出拓展欧几里得算法。
观察辗转相除法的代码,可以看到当结束递归时,b的值是0而a的值是gcd(a,b),于是可以得到方程的一个解:a*1+b*0 = gcd(a,b);此时x = 1,y = 0;但是这时的a,b的值已经不是最初开始调用的a,b的值了。为了求解x和y就必须要将式子递推回原本的样子。
思路:由于是递归生成,如果知道了递推的规律就可以恢复到最初的样子,解出答案来。
-
假设已经求出了某一个状态使得 b*x1+(a%b)*y1=gcd
-
这时我们可以试着去倒推两个相邻状态之间的关系:
- 首先我们知道:a%b=a-(a/b)*b;带入:
b*x1 + (a-(a/b)*b)*y1;
= b*x1 + a*y1 – (a/b)*b*y1;
= a*y1 + b*(x1 – a/b*y1) = gcd;
x = y1 , y = x1 – a/b*y1
- 首先我们知道:a%b=a-(a/b)*b;带入:
代码实现
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{
if(b==0)
{
x=1;y=0;
return a; //到达递归边界开始向上一层返回
}
int r=exgcd(b,a%b,x,y);
int temp=y; //把x y变成上一层的
y=x-(a/b)*y;
x=temp;
return r; //得到a b的最大公因数
}
//改进后代码
void extend_gcd(int a, int b, int &x, int &y){
//返回x,y,即一个特解(x0, y0)
if(b==0) {
x=1, y=0; return ;//结束标志
}
extend_gcd(b, a%b, y, x);
y = y - (a/b)*x;
}
应用
一、求解逆元
/*
给出a,m,求解 ax = 1(mod m)
即 ax除以m,余数是1
称x为a模m的逆
x的解答有很多
有解的条件gcd(a,m) = 1,即a、m互素
等价于ax + my = 1;于是可以使用拓展欧几里得算法求解
例:8x = 1(mod 31)
等价于求8x + 31y = 1;
*/
int mod_inverse(int a, int m){
int x, y;
extend_gcd(a, m, x, y);
return (m + x % m) % m; //x可能是负数
}
-
逆元的一个重要应用:求除法的模
(a/b) mod m
a除以b,然后对m取模
但是:a和b都是大数,如果做除法后再取模,会损失精度。(应用场景:catalan数)所以 需要避开除法计算
-
设b的逆元为k有
/* (a/b)mod m = ((a/b)mod m)*((b*k)mod m) = (a/b*b*k) mod m = (a*k) mod m */
二、韩信点兵类问题
/*
韩信点兵
韩信带贰仟伍佰士兵出去打仗,回营后,刘邦问士兵人数。
韩信让士兵先列成五行纵队,末行一人;
列成六行纵队,末行五人;
列成七行纵队,末行四人;列成十一行纵队,末行十人。
韩信立刻回答二千一百一十一人。刘邦惊为天人!
物不知数问题
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void extend_gcd(ll a,ll b,ll &x,ll &y) {
if(b==0) {
x = 1,y=0;
return;
}
extend_gcd(b,a%b,y,x);
y = y-(a/b)*x;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll a[1000],b[1000],c[1000],x,y;
int n;
cin >> n;
ll t = 1;
for (int i = 0;i<n;i++) {
cin >> a[i] >> c[i];
b[i] = a[i];
t *= a[i];
}
for (int i = 0;i<n;i++) {
a[i] = t/a[i];
}
ll res = 0;
for (int i = 0;i<n;i++) {
x = 0,y = 0;
extend_gcd(a[i],b[i],x,y);
//非负数
//ai关于模bi的逆元为:
//cout << (x % b[i] + b[i])%b[i] << endl;
res += a[i]*c[i]*((x % b[i] + b[i])%b[i]);
}
cout << res%t;
return 0;
}

浙公网安备 33010602011771号