欧几里得算法、扩展欧几里得算法、求逆元、中国剩余定理、扩展中国剩余定理

本文详细介绍了欧几里得算法及其扩展版本,包括求解最大公约数、线性同余方程等内容,并通过实际代码示例展示了算法的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欧几里得算法

求两个正整数 a 和 b 的 最大公约数 d
则有 gcd(a,b) = gcd(b,a%b)
证明
设a%b = a - kb 其中k = a/b(向下取整)
若d是(a,b)的公约数 则知 d|a 且 d|b 则易知 d|a-k
b 故d也是(b,a%b) 的公约数
若d是(b,a%b)的公约数 则知 d|b 且 d|a-kb 则 d|a-kb+k*b = d|a 故而d|b 故而 d也是(a,b)的公约数
因此(a,b)的公约数集合和(b,a%b)的公约数集合相同 所以他们的最大公约数也相同 证毕

欧几里得算法
利用gcd(a,b) = gcd(b,a%b)的性质,不断递归,直到b为0,那么a就是最大公约数。

ACWING872 最大公约数
给定 n 对正整数 ai,bi ,请你求出每对数的最大公约数。

输入格式
第一行包含整数 n 。

接下来 n 行,每行包含一个整数对 ai,bi 。

输出格式
输出共 n 行,每行输出一个整数对的最大公约数。

数据范围
1≤n≤10^5 ,
1≤ai,bi≤2×10^9
输入样例:

2
3 6
4 6

输出样例:

3
2
#include <bits/stdc++.h>
using namespace std;
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,a,b;
    cin>>n;
    while(n--)
    {
        cin>>a>>b;
        cout<<gcd(a,b)<<'\n';
    }
    return 0;
}

扩展欧几里得算法

1.扩展欧几里得
用于求解方程 ax+by=gcd(a,b) 的解
当 b=0时 ax+by=aax+by=a 故而 x=1,y=0
当 b≠0 时
因为
gcd(a,b)=gcd(b,a%b)

bx′+(a%b)y′=gcd(b,a%b)
bx′+(a−⌊a/b⌋∗b)y′=gcd(b,a%b)
ay′+b(x′−⌊a/b⌋∗y′)=gcd(b,a%b)=gcd(a,b)
故而
x=y′,y=x′−⌊a/b⌋∗y′
因此可以采取递归算法 先求出下一层的x′和y′再利用上述公式回代即可
2.对于更一般的方程 ax+by=c
设 d=gcd(a,b) 则其有解当且仅当 d|c
求解方法如下:

用扩展欧几里得求出 ax0+by0=d的解

则a(x0∗c/d)+b(y0∗c/d)=c
故而特解为 x′=x0∗c/d,y′=y0∗c/d
而通解 = 特解 + 齐次解

而齐次解即为方程 ax+by=0ax+by=0的解

故而通解为 x=x′+k∗b/d,x=y′−k∗a/dk∈z
3.应用: 求解一次同余方程 ax≡b(modm)ax≡b(modm)
则等价于求

ax=m∗(−y)+b

ax+my=b

有解条件为 gcd(a,m)|b然后用扩展欧几里得求解即可

特别的 当 b=1 且 a与m互质时 则所求的x即为a的逆元

ACWING877扩展欧几里得算法
给定 n 对正整数 ai,bi ,对于每对数,求出一组 xi,yi ,使其满足 ai×xi+bi×yi=gcd(ai,bi) 。

输入格式
第一行包含整数 n 。

接下来 n 行,每行包含两个整数 ai,bi 。

输出格式
输出共 n 行,对于每组 ai,bi ,求出一组满足条件的 xi,yi ,每组结果占一行。

本题答案不唯一,输出任意满足条件的 xi,yi 均可。

数据范围
1≤n≤10^5 ,
1≤ai,bi≤2×10^9
输入样例:

2
4 6
8 18

输出样例:

-1 1
-2 1

AC代码:

#include <bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    int gcd,x1,y1;
    gcd=exgcd(b,a%b,x1,y1);
    x=y1,y=x1-a/b*y1;
    return gcd;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,x,y,a,b;
    cin>>n;
    while(n--)
    {
        cin>>a>>b;
        exgcd(a,b,x,y);
        cout<<x<<' '<<y<<'\n';
    }
    return 0;
}

注意点:这里用引用返回x和y的值

扩展欧几里得算法的应用1:线性同余方程

ACWING878 线性同余方程
给定 n 组数据 ai,bi,mi ,对于每组数求出一个 xi ,使其满足 ai×xi≡bi(modmi) ,如果无解则输出 impossible。

输入格式
第一行包含整数 n 。

接下来 n 行,每行包含一组数据 ai,bi,mi 。

输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi ,如果无解则输出 impossible。

每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。

输出答案必须在 int 范围之内。

数据范围
1≤n≤10^5 ,
1≤ai,bi,mi≤2×10^9
输入样例:

2
2 3 6
4 3 5

输出样例:

impossible
-3

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    int gcd,x1,y1;
    gcd=exgcd(b,a%b,x1,y1);
    x=y1,y=x1-a/b*y1;
    return gcd;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m,a,b,x,y;
    cin>>n;
    while(n--)
    {
        cin>>a>>b>>m;
        int d=exgcd(a,m,x,y);
        if(b%d)cout<<"impossible"<<'\n';
        else
        {
            x=(LL)x*b/d%m;
            cout<<x<<'\n';
        }
    }
    return 0;
}

注意点:
// a * x ≡ b (mod m)
// 变形为拓展欧几里得形式:a * x + b * y = gcd(a, b)
// 原式变为: a * x = m * y + b (注:mod m 为 b, 则相当于结果为 m 的倍数和 b 的和)
// a * x - m * y = b
// 另y1 = -y得:a * x + m * y1 = b
// 根据拓展欧几里得定理,只要 b 是 gcd(a, m)的倍数即有解!
// 另d = gcd(a, m), 我们得到的式子其实是:a * x + m * y1 = gcd(a, m) = d (注;上面的b其实就是d的倍数)
// 所以左右同乘 b / d 即可转化为:a * x * b / d + m * y1 * b / d = d * b / d = b
// 即最后答案为:res = x * b / d % m

求逆元(快速幂、扩展欧几里得算法的应用)

当n为质数时,可以用快速幂求逆元:
a / b ≡ a * x (mod n)
两边同乘b可得 a ≡ a * b * x (mod n)
即 1 ≡ b * x (mod n)
同 b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得 b * b ^ (n - 2) ≡ 1 (mod n)
故当n为质数时,b的乘法逆元 x = b ^ (n - 2)

当n不是质数时,可以用扩展欧几里得算法求逆元:
a有逆元的充要条件是a与p互质,所以gcd(a, p) = 1
假设a的逆元为x,那么有a * x ≡ 1 (mod p)
等价:ax + py = 1
exgcd(a, p, x, y)

ACWING876快速幂求逆元
给定 n 组 ai,pi ,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。

注意:请返回在 0∼p−1 之间的逆元。

乘法逆元的定义
若整数 b,m 互质,并且对于任意的整数 a ,如果满足 b|a ,则存在一个整数 x ,使得 a/b≡a×x(modm) ,则称 x 为 b 的模 m 乘法逆元,记为 b^−1(modm)
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时, b^m−2 即为 b 的乘法逆元。

输入格式
第一行包含整数 n 。

接下来 n 行,每行包含一个数组 ai,pi ,数据保证 pi 是质数。

输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。

若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。

数据范围
1≤n≤10^5 ,
1≤ai,pi≤2∗10^9
输入样例:

3
4 3
8 5
6 3

输出样例:

1
2
impossible

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL fp(int a,int b,int m)
{
    LL res=1;
    while(b)
    {
        if(b&1)res=res*a%m;
        b>>=1;
        a=(LL)a*a%m;
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int a,p,n;
    cin>>n;
    while(n--)
    {
        cin>>a>>p;
        if(a%p==0)cout<<"impossible"<<'\n';
        else
        {
            cout<<fp(a,p-2,p)<<'\n';
        }
    }
    return 0;
}

注意点:

  • a%p==0不能写成!a%p,因为c++会把!和a绑定在一起,如果要用的话,记得加括号。
  • 因为p是质数,它与所有不是它倍数的数互质。因此,判断a与p互质只需要看a是不是p的倍数就行了。
  • 数学问题,一定要多用long long。

中国剩余定理

在这里插入图片描述

ACWING1298 曹冲养猪
自从曹冲搞定了大象以后,曹操就开始琢磨让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲很不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。

举个例子,假如有 16 头母猪,如果建了 3 个猪圈,剩下 1 头猪就没有地方安家了;如果建造了 5 个猪圈,但是仍然有 1 头猪没有地方去;如果建造了 7 个猪圈,还有 2 头没有地方去。

你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?

输入格式
第一行包含一个整数 n ,表示建立猪圈的次数;

接下来 n 行,每行两个整数 ai,bi ,表示建立了 ai 个猪圈,有 bi 头猪没有去处。

你可以假定 ai,aj 互质。

输出格式
输出仅包含一个正整数,即为曹冲至少养猪的数目。

数据范围
1≤n≤10 ,
1≤bi≤ai≤1100000
所有 ai 的乘积不超过 10^18
输入样例:

3
3 1
5 1
7 2

输出样例:

16

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL m[12];
int a[12], b[12];
LL exgcd(LL a, LL b, LL &x, LL &y)
{
    if(b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    LL gcd, x1, y1;
    gcd = exgcd(b, a % b, x1, y1);
    x = y1, y = x1 - a / b * y1;
    return gcd;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    LL M = 1, res = 0;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i];
        M *= a[i];
    }
    for(int i = 1; i <= n; i++)
    {
        m[i] = M / a[i];
        LL x, y;
        exgcd(m[i], a[i], x, y);
        res += m[i] * x * b[i];
    }
    cout << ((res % M) + M) % M;
    return 0;
}

注意点:

  • 用long long
  • 最后的res有可能为负数(c++取余机制),所以要用((res % M) + M) % M把它变成正数。

扩展中国剩余定理

在这里插入图片描述

ACWING204 表达整数的奇怪方式
给定 2n 个整数 a1,a2,…,an 和 m1,m2,…,mn ,求一个最小的非负整数 x ,满足 ∀i∈[1,n],x≡mi(mod ai) 。

输入格式
第 1 行包含整数 n 。

第 2…n+1 行:每 i+1 行包含两个整数 ai 和 mi ,数之间用空格隔开。

输出格式
输出最小非负整数 x ,如果 x 不存在,则输出 −1 。
如果存在 x ,则数据保证 x 一定在 64 位整数范围内。

数据范围
1≤ai≤2^31−1 ,
0≤mi<ai
1≤n≤25
输入样例:

2
8 7
11 9

输出样例:

31

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    LL gcd,x1,y1;
    gcd=exgcd(b,a%b,x1,y1);
    x=y1,y=x1-a/b*y1;
    return gcd;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    LL a1,a2,k1,k2,m1,m2,x;
    cin>>n;
    cin>>a1>>m1;
    n--;
    while(n--)
    {
        cin>>a2>>m2;
        LL gcd=exgcd(a1,-a2,k1,k2);
        if((m2-m1)%gcd)
        {
            //cout<<m2<<' '<<m1<<' '<<gcd<<'\n';
            x=-1;
            break;
        }
        k1*=(m2-m1)/gcd;
        LL t=abs(a2/gcd);
        k1=(k1%t+t)%t;
        m1=k1*a1+m1;
        a1=abs(a1/gcd*a2);
    }
    if(x!=-1)x=(m1%a1+a1)%a1;
    cout<<x;
    return 0;
}

注意点:

  • 由于不知道a1、a2的正负,所以lcm(a1,a2)和a2/d要加上绝对值
  • 记得LL和求余后取正的操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值