扩展欧几里得算法


扩展欧几里得

内容

梗概

对于任意两个数$a, b$,两者间必有最大公因子$g$,使得$a = k_1g,b = k_2g$ ;

可得必有整数$x, y$ 使$ax + by = gcd(a,b)$

板子

typedef long long ll;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if(!b) {
        x = 1;
        y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

bool find_any_solution(ll a, ll b, ll c, ll &x0, ll &y0, ll &g)
{
	g = ex_gcd(abs(a), abs(b), x0, y0);
	if (c % g)
		return false;
	x0 *= c / g;
	y0 *= c / g;
	if (a < 0) x0 = -x0;
	if (b < 0) y0 = -y0;
	return true;
}

丢番图求解

丢番图求一个解

//有解返回true,无解返回false
bool find_one_solution(ll a, ll b, ll &x, ll &y, ll C) {
    ll d = exgcd(abs(a), abs(b), x, y);
    if(C % d) return false;
    
    x *= C / d;
    y *= C / d;
    if(a < 0) x = -x;
    if(b < 0) y = -y;
    return true;
}
扩展欧几里得基本应用

https://ac.nowcoder.com/acm/contest/21289/A

题目描述:

求关于 $x$ 的同余方程$ax≡1(mod\ b)$的最小正整数解,若无解,输出”-1”。

对题目的方程进行变化,可以得到 $a x + b y = 1$ ,故而对其求 $x$ 的最小正整数解即可(中间计算时可能会爆 int , 故最好用 long long存储 )

#include <iostream>

using namespace std;

using ll  = long long;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if(!b) {
        x = 1;
        y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

void solve() {
    ll a, b;
    cin >> a >> b;
    ll x, y;
    ll d = exgcd(a, b, x, y);
    //此处因为C是1,所以如果b不为1就无解
    if(d != 1) {
        puts("-1");
        return;
    }
    //因为d是1,所以b/d = b
    x = (x % b + b) % b;
    cout << x << endl;
}

int main() {
    int T;
    cin >> T;
    while(T --) 
        solve();
    return 0;
}

中国剩余定理(线性同余方程)

线性同余方程求解是扩展欧几里得的求解应用之一,大意是需要求一个 $x$ , $x$ 满足以下条件 $x$$m_i$ $(mod\ a_i)$ 形式大体如下

$$
x= \left{ \begin{aligned} m_1\ (mod\ a_1) \ m_2\ (mod\ a_2) \ \vdots \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ m_n\ (mod\ a_n) \end{aligned} \right.
$$

对于以上公式,可以进行变形得:

$$
x=\left{ \begin{aligned} m_1 + k_1a_1\ (1) \ m_2+k_2a_2\ (2) \ \vdots \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ m_n + k_na_n\ (n) \end{aligned} \right.
$$

由 $(1)\ (2)$ 可推导得出 :
$$
m_1 + k_1a_1 = m_2 + k_2a_2

​ => k_1a_1 - k_2a_2 = m_2 - m_1
$$
通过扩展欧几里得可以求出以上丢番图的一个解 $x_0\ y_0$ $(d = exgcd(a1, a2, x0, y0))$

若 $d$ 不能被 $m2 - m1$ 整除,则证明无解(扩展欧几里得里的解释),否则有

有 $k_1 = x_0 + k \times \lfloor \frac{a2}{d} \rfloor $

之后再将 $k_1$ 带入 $(1)$ 可得 :

​ $x = m_1 + k_1a_1$

​ $x = m_1 + (x_0 + k \times \lfloor \frac{a_2}{d} \rfloor) \times a_1$

​ $x = m_1 + x_0 \times a_1 + k \times \lfloor \frac{a_1a_2}{d} \rfloor $

故可得一个新的关系式:

​ $x = m_0 + k \times a_0$

其中:$m_0=m_1 + x_0 \times a_1$ ; $ a_0 = \lfloor \frac{a_1a_2}{d} \rfloor $

又因为$d$ 是$a_1a_2$ 的最大公约数,所以 $\lfloor \frac{a_1a_2}{d} \rfloor$ 是$a_1 a_2$ 的最小公倍数,综上,可以不断对线性方程组进行合并,得到最后的答案 $x = m\ (mod\ a)$


中国剩余定理基本应用

求线性方程组的解:(链接)https://ac.nowcoder.com/acm/contest/21289/D

解线性同余方程组的板子题,注意计算中间值时可能会溢出 $long\ long$ 适当用 $ {__int 128_t} $ 作为中间值

#include <iostream>

using namespace std;
using u128 = __int128_t;
using ll  = long long;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if(!b) {
        x = 1;
        y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int n;
    cin >> n;
    //将第一对a1,m1记录
    ll a1, m1;
    cin >> a1 >> m1;

    //标记是否有解
    bool flag = false;

    //因为已经读入一对a,m了,所以就循环n-1次就足够了
    for(int i = 1; i < n; i++) {
        ll a2, m2;
        cin >> a2 >> m2;

        //扩展欧几里得的计算
        ll C = (m2 - m1), x0, y0;
        ll d = exgcd(a1, a2, x0, y0);

        //判断是否有解
        if(C % d) {
            flag = true;
            break;
        }

        //计算答案
        ll t = abs(a2 / d);
        
        x0 %= t;
        x0 = ((u128)x0 * C / d) % t;

        m1 += x0 * a1;
        a1 *= t;
    }
    if(flag) {
        puts("-1");
    }
    else {
        cout << (m1 % a1 + a1) % a1 << endl;
    }
    return 0;
}

相关链接

丢番图解释

https://gcuacm.gitee.io/2019/03/18/190318/


文章作者: 江南小狗
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江南小狗 !
  目录