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

中国剩余定理

\(m_1, m_2,\cdots, m_n\)两两互质的整数,\(m = \prod_{i=1}^{n} m_i\)\(M_i = m / m_i\)(除了 \(m_i\) 之外其他所有 \(m\) 的乘积),\(t_i\) 是线性同余方程 \(M_it_i = 1 \pmod {m_i}\) 的一个解,即 \(t_i\)\(M_i\)\(m_i\) 的乘法逆元。对于任意的 \(n\) 个整数 \(a_1, a_2, \cdots, a_n\) ,方程组

\[\begin{cases} x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ \dots \\ x \equiv a_n \pmod {m_n} \end{cases} \]

有整数解,解为 \(x = \sum_{i=1}^{n} a_i M_i t_i\)

证明
因为 \(M_i = m / m_i\) 是除 \(m_i\) 之外所有模数的倍数,所以 \(\forall k \neq i, a_iM_it_i \equiv 0 \pmod {m_k}\)
又因为 \(a_iM_it_i \equiv a_i \pmod {m_i}\) ,所以代入 \(x = \sum_{i=1}^{n} a_iM_it_i\),原方程组成立。
证毕

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

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

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

本题中 \(m_i\) 不一定两两互质,中国剩余定理不再适用。可以考虑使用数学归纳法,假设已经求出了前 \(k-1\) 个方程构成的方程组的一个解 \(x\)。记 \(m = lcm(m_1,m_2, \cdots, m_k-1)\) ,则 \(x+i*m(i \in \mathbb{Z})\) 是前 \(k-1\) 个方程的通解。

以前两个方程举例说明:

\[x \equiv m_1 \pmod {a_1} \\ x \equiv m_2 \pmod {a_2} \]

\[x = k_1a_1 + m_1 \\ x = k_2a_2 + m_2 \\ k_1a_1 + m1 = k_2a_2 + m_2 \\ k_1a_1 - k_2a_2 = m_2 - m_1 \]

于是问题转化为,在已知 \(a_1, a_2, m_2-m_1\) 的前提下,求一组 \(k_1, k_2\) 满足以上线性同余方程,而线性同余方程有解的充要条件是 \(gcd(a_1,a_2) | m_2 - m_1\)
根据扩展欧几里得算法,我们可以先求出线性同余方程 \(k_1'a_1 - k_2'a_2 = gcd(a_1, a_2)\) 的一组特解 \((k_1', k_2')\),再将方程两边同时乘以 \(\frac{m_2-m_1}{gcd(k_1,k_2)}\),即可得到方程 \(k_1a_1 - k_2a_2 = m_2 - m_1\) 的一组特解:

\[k_1 = k_1' \cdot \frac{m_2-m_1}{gcd(k_1,k_2)} \\ k_2 = k_2' \cdot \frac{m_2-m_1}{gcd(k_1,k_2)} \]

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

\[k_{1T} = k_1 + k \cdot \frac{a_2}{gcd(a_1,a_2)} \\ k_{2T} = k_2 - k \cdot \frac{a_1}{gcd(a_1,a_2)} \]

\(gcd(a_1,a_2) = d\) ,于是

\[x = k_{1T}a_1 + m_1 = (k_1 + k \cdot \frac{a_2}{d})\cdot a_1 + m_1 = a_1k_1 + m_1 + k \cdot \frac{a_1a_2}{d} = a_1k_1 + m_1 + k \cdot lcm(a_1a_2) \]

\(a_1k_1 + m_1 = m\)\(lcm(a_1,a_2) = a\),则我们将方程组内前两个方程合为一个方程:

\[x = k \cdot a + m \]

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

\[x \equiv m \pmod a \]

#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 @ 2022-12-10 09:34  S!no  阅读(108)  评论(0编辑  收藏  举报