算法学习笔记(38)——中国剩余定理

中国剩余定理

m1,m2,,mn两两互质的整数,m=i=1nmiMi=m/mi(除了 mi 之外其他所有 m 的乘积),ti 是线性同余方程 Miti=1(modmi) 的一个解,即 tiMimi 的乘法逆元。对于任意的 n 个整数 a1,a2,,an ,方程组

{xa1(modm1)xa2(modm2)xan(modmn)

有整数解,解为 x=i=1naiMiti

证明
因为 Mi=m/mi 是除 mi 之外所有模数的倍数,所以 ki,aiMiti0(modmk)
又因为 aiMitiai(modmi) ,所以代入 x=i=1naiMiti,原方程组成立。
证毕

中国剩余定理给出了模数两两互质的线性同余方程组的一个特解。方程组的通解可以表示为 x+km(kZ) 。有些题目要求我们求出最小的非负整数解,只需把 xm 取模,并让 x 落在 0 ~ m1 的范围内即可。

另外,及时模数不满足两两互质,我们也有方法判断线性同余方程是否有解,并求出方程组的解。

题目链接:AcWing 204. 表达整数的奇怪方式

本题中 mi 不一定两两互质,中国剩余定理不再适用。可以考虑使用数学归纳法,假设已经求出了前 k1 个方程构成的方程组的一个解 x。记 m=lcm(m1,m2,,mk1) ,则 x+im(iZ) 是前 k1 个方程的通解。

以前两个方程举例说明:

xm1(moda1)xm2(moda2)

x=k1a1+m1x=k2a2+m2k1a1+m1=k2a2+m2k1a1k2a2=m2m1

于是问题转化为,在已知 a1,a2,m2m1 的前提下,求一组 k1,k2 满足以上线性同余方程,而线性同余方程有解的充要条件是 gcd(a1,a2)|m2m1
根据扩展欧几里得算法,我们可以先求出线性同余方程 k1a1k2a2=gcd(a1,a2) 的一组特解 (k1,k2),再将方程两边同时乘以 m2m1gcd(k1,k2),即可得到方程 k1a1k2a2=m2m1 的一组特解:

k1=k1m2m1gcd(k1,k2)k2=k2m2m1gcd(k1,k2)

该方程的通解可以表示为:

k1T=k1+ka2gcd(a1,a2)k2T=k2ka1gcd(a1,a2)

gcd(a1,a2)=d ,于是

x=k1Ta1+m1=(k1+ka2d)a1+m1=a1k1+m1+ka1a2d=a1k1+m1+klcm(a1a2)

a1k1+m1=mlcm(a1,a2)=a,则我们将方程组内前两个方程合为一个方程:

x=ka+m

对后续方程递归进行上述操作,即可将方程组合并为一个方程,最后得出:

xm(moda)

#include <iostream>

using namespace std;

typedef long long LL;

// 扩展欧几里得算法,返回a与b的最大公约数,同时求出ax+by=gcd(a,b)的解(x,y)
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;
    
    bool has_answer = true; // 代表是否有解
    LL a1, m1; // 每次将一组新的方程合并入现有方程
    
    cin >> a1 >> m1; //读入第一个方程
    
    // 读入之后的n-1个方程
    for (int i = 0; i < n - 1; i ++ ) {
        LL a2, m2;
        cin >> a2 >> m2;
        
        LL k1, k2;
        LL d = exgcd(a1, a2, k1, k2);
        // 若不满足线性同余方程有解的充要条件 => gcd(a1,a2) | m2 - m1,则跳出循环,置为false
        if ((m2 - m1) % d) {
            has_answer = false;
            break;
        }
        
        // 转换特解为通解 k1a1 - k2a2 = gcd(a1,a2) => k1a1 - k2a2 = m2 - m1
        k1 *= (m2 - m1) / d;
        
        LL t = a2 / d;
        k1 = (k1 % t + t) % t;  // 在转换过程中求出最小的非负整数解,防止溢出
        
        // 更新合并后的a1 与 m1
        m1 = a1 * k1 + m1;
        a1 = abs(a1 / d * a2);
    }
    
    // C++ 取模可能得到负数,所以对取模的结果加m1再模m1,即可转为正数
    if (has_answer) cout << (m1 % a1 + a1) % a1 << endl;
    else puts("-1");
    
    return 0;
}
posted @   S!no  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示