中国剩余定理及其扩展定理 学习笔记

中国剩余定理及其扩展定理 学习笔记

中国剩余定理,又叫孙子定理,最早出现在我国古代著作《孙子算经》中,OI 中常称其为 CRT(China Remainder Theorem)。

问题

CRT 用于求解线性同余方程组问题,且模数互质:

\[(a_1, a_2, ..., a_n) = 1\\\begin{cases} x \equiv b_1\ ({\rm mod}\ a_1) \\ x\equiv b_2\ ({\rm mod}\ a_2) \\ ... \\ x \equiv b_n\ ({\rm mod}\ a_n)\end{cases} \]

\(x\) 的最小非负整数解。

CRT

\(m_i = \dfrac{\prod_{j = 1}^n a_j}{a_i}, r_i\equiv m_i^{-1}\pmod {a_i}\)

\[x_0 = \sum_{i = 1}^nb_im_ir_i \]

\(x_0\) 是一个特解,通解形式如下,因为对于每一个 \(a_i\) 都有 \(a_i | \prod a_i\)

\[x = x_0 + k\prod_{i = 1}^na_i \]

显然地,最小的非负整数解就是 \((x_0\bmod \prod a_i + \prod a_i) \bmod \prod a_i\)

这其实是一个构造性的算法,接下来证明 \(x_0\) 是方程的解。

证明:\(\forall i\in[1, n]\)\(x\equiv b_i\pmod {a_i}\)\(x_0\) 是其一个解。

对于 \(j\ne i, a_i | m_j\),所以 \(x_0\equiv b_im_ir_i\pmod {a_i}\)

\(m_ir_i\equiv 1\pmod{a_i}\),所以 \(x_0\equiv b_i\pmod {a_i}\).

注意到 \(r_i\)\(m_i\)\(\bmod a_i\) 意义下的逆元,由于 \(a\) 序列互相互质,所以一定存在逆元,但在去掉这个限制的情况下,逆元不存在,无法通过 CRT 求解。

ExCRT

用于解决线性同余方程组模数不互质的情况,ExCRT 其实和 CRT 没什么关系

考虑一个同余方程组:

\[\\\begin{cases} x \equiv b_1\ ({\rm mod}\ a_1) \\ x\equiv b_2\ ({\rm mod}\ a_2)\end{cases} \]

如果能把它们两个合并成一个方程,然后迭代地合并所有方程,最后得到一个唯一的同余方程,岂不美哉?

先考虑把方程拿出同余系:

\[\begin{cases} x = b_1 + pa_1 \\ x = b_2 + qa_2\end{cases}\Longrightarrow b_2 + qa_2 = b_1 + pa_1 \Longrightarrow pa_1 - qa_2 = b_2 - b_1 \]

发现这是一个 ExGcd(aka 裴蜀定理)的形式,方程无解当且仅当 \(\gcd(a_1, a_2) \nmid (b_2 - b_1)\),于是可以通过 ExGcd 求出 \(p, q\) 的特解,设其为 \(p_0, q_0\),则 \(p, q\) 的通解为:

\[\begin{cases}p = p_0 + \dfrac{a_2}{\gcd(a_1, a_2)}t \\ q = q_0 + \dfrac{a_1}{\gcd(a_1, a_2)}t\end{cases} \]

所以把通解代入一开始其中一个方程中得到:

\[x = b_1 + p_0a_1 + \dfrac{a_1a_2}{\gcd(a_1,a_2)}t\Longrightarrow \\ x = b_1 + p_0a_1 + \text{lcm}(a_1, a_2)t\Longrightarrow \\ x\equiv b_1+p_0a_1\pmod{\text{lcm}(a_1, a_2)} \]

至此,我们将两个同余方程合并为一个同余方程,重复 \(n - 1\) 次合并,最终剩下一个同余方程,这样最小正整数解就呼之欲出了。

C++ 实现

Problem: P4777 【模板】扩展中国剩余定理(EXCRT)

// Problem: P4777 【模板】扩展中国剩余定理(EXCRT)
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-11-11 13:26:13

#include <algorithm>
#include <iostream>
#define int long long
#define ll __int128
using namespace std;
const int N = 1e5 + 10;

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

int excrt() {
    for(ll i = n - 1, k1, k2, d; i; i --) {
        d = __gcd(p[i], p[i + 1]);
        if((a[i + 1] - a[i]) % d) return -1; // 本题中不存在无解,但是无解情况不要漏
        exgcd(p[i], k1, p[i + 1], k2);
        k1 = k1 * (a[i + 1] - a[i]) / d % (p[i + 1] / d);
        a[i] += k1 * p[i], p[i] = p[i] / __gcd(p[i], p[i + 1]) * p[i + 1], a[i] %= p[i];
    }
    return (a[1] % p[1] + p[1]) % p[1];
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> p[i] >> a[i];
    cout << excrt() << '\n';

    return 0;
}
posted @ 2023-11-13 20:47  MoyouSayuki  阅读(12)  评论(0编辑  收藏  举报
:name :name