拓展欧几里得算法详解

拓展欧几里得算法详解

贝祖定理

​ 即如果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
代码实现
#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取模

    但是:ab都是大数,如果做除法后再取模,会损失精度。(应用场景: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;
}
posted @ 2020-12-11 13:42  0xYuk1  阅读(285)  评论(0)    收藏  举报