中国剩余定理

发布时间 2023-04-07 21:13:09作者: 2huk

中国剩余定理

有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。

题目描述

给定 \(n\) 个整数 \(a_i\)\(n\) 个整数 \(m_i\),并保证 \(m_i\) 两两互质,求一个整数 \(x\),使得

\[\left\{\begin{matrix}x \equiv a_1 \pmod {m_1} \\x \equiv a_2 \pmod {m_2} \\\dots \dots \dots \dots \\x \equiv a_n \pmod {m_n} \end{matrix}\right. \]

规定

规定 \(M\) 表示所有 \(m_i\) 的乘积,\(M_i\) 表示除了 \(m_i\) 之外的所有数的乘积。

\(M = m_1\cdot m_2\cdots m_n\)\(M_i = \dfrac{M}{m_i}\)

\(M_i^{-1}\) 规定为 \(M_i\)\(m_i\) 的逆元。

公式

\(x = \sum_{i=1}^{n}a_i \cdot M_i \cdot M_i^{-1}\)

推导

从前两个方程进行考虑:

\[x\mod a_1 = m_1\\ x \mod a_2 = m_2 \]

将其变形,得到:

\[x = k_1 \cdot a_1 + m_1\\ x = k_2 \cdot a_2 + m_2 \]

由于上下式结果都是 \(x\),还可以进一步的简化:

\[k_1 \cdot a_1 + m_1 = k_2 \cdot a_2 + m_2 \]

移项得:

\[k_1 \cdot a_1 - k_2 \cdot a_2 = m_2 - m_1 \]

上式中 \(a_1\)\(a_2\)\(m_2 - m_1\) 都是已知量,可以用扩展欧几里得算法求解 \(k_1\)\(k_2\)

如果最初的方程有解,就等价于 \(\gcd(a_1, a_2) \mid (m_2 - m_1)\)

上述不定方程的所有解可以表示成以下形式:

\[\left\{\begin{matrix} k_1 + k \cdot \dfrac{a_2}{d} \\ k_2 + k \cdot \dfrac{a_1}{d} \end{matrix}\right. \]

那么 \(x\) 的所有解就可以写成 \(\left( k_1 + k \cdot \dfrac{a_2}{d} \right) \cdot a_1 + m\)

其中 \(k\) 是一个不确定的数,\(d\)\(a_1\)\(a_2\) 的最大公约数。

进一步整理:

\[x = a_1 \cdot k_1 + m_1 + k \cdot \dfrac{a_1 \cdot a2}{d} \]

其中,\(\dfrac{a_1 \cdot a2}{d}\) 相当于 \(a_1\)\(a_2\) 的最小公倍数。

可得:

\[x = a_1 \cdot k_1 + m_1 + k \cdot lcm(a, b) \]

此时,前面的 \(a_1 \cdot k_1 + m_1\) 是固定的,记为 \(x_0\),后面的 \(lcm(a, b)\) 记为新的 \(a\)

整理可得:

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

这时,回到上面,我们会发现这个式子和上面

\[x = k_1 \cdot a_1 + m_1\\ x = k_2 \cdot a_2 + m_2 \]

很像,所以此时可以将两个式子合并,即

\[\left.\begin{matrix} x = k_1 \cdot a_1 + m_1\\ x = k_2 \cdot a_2 + m_2 \end{matrix}\right\}x = x_0 + k \cdot a \]

再用这个合并的方程与下面的方程继续合并,那么合并 \(n-1\) 次后就可以直接求解了。

代码

#include <iostream>

using namespace std;

#define int long long

int n;
int a1, m1;     // 现有的方程
int a2, m2;     // 需要合并的方程
bool flag = true;       // flag 表示当前是否无解
int k1, k2, d, t;

int mod(int a, int b){
    return (a % b + b) % b;
}

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

signed main(){
    cin >> n >> a1 >> m1;
    
    for(int i=1; i<n; i++){
        cin >> a2 >> m2;
        
        // 求出系数
        d = exgcd(a1, a2, k1, k2);
        
        // 判断是否无解
        if((m2 - m1) % d){
            flag = false;
            break;
        }
        
        k1 *= (m2 - m1) / d;
        t = a2 / d;
        k1 = mod(k1, t);
        m1 = a1 * k1 + m1;
        a1 = abs(a1 / d * a2);
    }
    
    if(flag){
        cout << mod(m1, a1);
    }
    else{
        puts("-1");
    }
    
    return 0;
}