中国剩余定理及其变形

中国剩余定理

解方程组:

\[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;
}
posted @ 2021-12-21 18:34  tsrigo  阅读(70)  评论(0编辑  收藏  举报