1. 程式人生 > 其它 >中國剩餘定理及其變形

中國剩餘定理及其變形

中國剩餘定理

解方程組:

\[x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ ... \\ x \equiv a_k \pmod {m_k} \\ 其中m_1, m_2...m_k兩兩互質 \]

\[M = \prod^{k}_{i = 1} m_i \\ M_i = \frac{M}{m _ i} \]

公式解:

\[x = \sum^{k}_{i = 1} a_iM_iM_i^{-1} \]

\(M_i^{-1}\)

\(n\)意義下,非零整數\(x\)的逆元存在的充要條件是:\(x\)\(n\)互質

因為\(M_i\)

\(m_i\)互質,故\(M_i^{-1}(M_i的逆元)\)可求

求逆元:應用拓展歐幾里得演算法,解方程

\[M_i \cdot M_i^{-1} \equiv 1 \pmod {m_i} \]

變形(AcWing 204. 表達整數的奇怪方式)

解方程組:求一個最小的非負整數\(x\)滿足

\[x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ ... \\ x \equiv a_k \pmod {m_k} \\ 其中m_1, m_2...m_k不一定兩兩互質 \]

策略:兩兩合併(我也不知道是怎麼想到的==

先考慮前兩個方程,將其轉化為等式形式

\[x = k_1 \cdot a_1 + m_1(1) \\ x = k_2 \cdot a_2 + m_2(2) \\ 做差得\\ ka_1 - ka_2 = m_2 - m _1,形如ax+by = m,可使用拓展歐幾里得演算法求解k_1,k_2 \\ 有解條件:(a_1, a_2) |m_2 - m _ 1 \\ 設通解為 k = k_1 + \lambda \frac{a_2}{d} \]

\(通解k\)代入(1)得:

\[\begin{align*} x & = a_1k_1 + m_1 + k_1 \frac{a_1 a_2}{d}\\ & =a_1k_1 + m_1 + k_1[a_1,a_2] \\ & = x_0 + k_1a' \end{align*} \]

但是在實際程式執行過程中,由於資料範圍比較極限,所以過程中要縮小\(k_1\)

,於是我們把\(k_1\)中的因子\(t = \frac{a_2}{d}\)都除掉

\[令k’ = (k_1 \bmod t + t) \bmod t \]

這個\(k'\)是同時滿足(1),(2)的,在程式碼中我們把\(k_1\)替換為\(k'\)

同樣的\(x_0\)也是同時滿足(1),(2)的,所以滿足方程

\[x = x_0 + k_1a' \]

的解就同時是(1),(2)的解

以上,我們就得到了一條新的方程,它的解同時滿足(1)和(2),於是我們只要合併n - 1次就能得到一條解滿足所有方程的方程

\[x' = x_0' + k'a'' \\ 其中x_0',a''已知 \]

題目要使\(x\)最小且非負,我們只需把\(x'\)中的因子\(a''\)都除掉即可

\[答案 = (x_0' \bmod a'' + a'') \bmod a'' \]

p.s.上述很多過程在程式中體現為重複的賦值

程式碼

#include<iostream>
using namespace std;

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;
}
int main(void){
    
    int n;
    cin >> n;

    bool flag = 1;                      //記錄是否無解
    ll a1, m1;
    cin >> a1 >> m1;
    for (int i = 0; i < n - 1; i ++ ){  //每次把一個新的方程合併到現有方程之中
        ll a2, m2, k1, k2;
        cin >> a2 >> m2;

        ll d = exgcd(a1, a2, k1, k2);
        if ((m2 - m1) % d != 0){
            flag = 0;
            break;
        }                               //求了之後要用的其實只有k1
        k1 *= (m2 - m1) / d;            //用拓歐記得要放大
        ll t = a2 / d;                  //由於資料範圍比較極限,過程中要縮小k,使k變成通解當中的最小正整數解
        k1 = (k1 % t + t) % t;          
        m1 = a1 * k1 + m1;              //重新整理m
        a1 = abs(a1 / d * a2);          //重新整理a1,使其變成a1,a2的最小公倍數,除法在前放溢位
    }
    if (flag){
        cout << (m1 % a1 + a1) % a1 << endl;
    }
    else
        puts("-1");
    return 0;
}