中国剩余定理(孙子定理):数论中的一个关于一元线性同余方程组的定理,说明了一元线性同余方程组有解的准则以及求解方法。

需要预备知识:扩展欧几里得算法

中国剩余定理

适用问题

有以下一元线性同余方程组:

(S):{xa1(modm1)xa2(modm2)xan(modmn)

整数 m1,m2,,mn 其中任两数互质,求解 x.

求解步骤

通解可以用如下方式构造得到:

  1. M=m1×m2××mn=i=1nmi 是整数 m1,m2,,mn 的乘积,并设 Mi=M/mi,i{1,2,,n},即 Mi 是除了 mi 以外的 n1 个整数的乘积。
  2. ti=Mi1Mimi 的数论倒数:tiMi1(modmi),i{1,2,,n}.
  3. 方程组 (S) 的通解形式为:x=a1t1M1+a2t2M2++antnMn+kM=kM+i=1naitiMi,kZ. 在模 M 的意义下,方程组 (S) 只有一个解:x=i=1naitiMi.

来源:Wikipedia (使用 CC BY-SA 3.0 协议)

其中,求数论倒数(即乘法逆元)的方式是扩展欧几里得算法,不可使用费马小定理。

简单证明

x=a1t1M1+a2t2M2++antnMn+kM=kM+i=1naitiMi,kZ.

我们验证该解是否满足原一元线性同余方程组,首先考虑方程组中第一个方程:

xmodm1=(a1t1M1)modm1+(a2t2M2)modm1++(antnMn)modm1+(kM)modm1

首先,由于 t1M11(modm1),因此 (a1t1M1)modm1=a1modm1. 另外由于 Mi,i{2,3,,n} 中均包含 m1 因子,因此 (aitiMi)modm1=0,i{2,3,,n}. 综上 xmodm1=a1modm1,即 xa1(modm1).

同理可得,该解满足方程组中的所有方程,因此它是方程组的一个解。

扩展中国剩余定理

我们注意到,中国剩余定理的使用条件是整数 m1,m2,,mn 其中任两数互质。如果不满足该条件,中国剩余定理失效。

针对这种情况,我们使用扩展中国剩余定理求解。(该情况有可能无解)

适用问题

有以下一元线性同余方程组:

(S):{xa1(modm1)xa2(modm2)xan(modmn)

不保证整数 m1,m2,,mn 其中任两数互质,问:方程组是否有解?若有解,试求 x.

推导和求解步骤

联立其中两个方程

首先考虑 (S) 中前两个方程:

(1){xa1(modm1)xa2(modm2)

(1) 经过变形可得到:

(2){x=a1+m1k1x=a2+m2k2

求解联立方程组

消去 (2)x 并进行变形,得:

(3)m1k1m2k2=a2a1

该式对应的裴蜀等式为 (4),其中 d=gcd(m1,m2)

(4)m1k1m2k2=d

通过扩展欧几里得算法,求得 k1,k2,d,若 a2a1d 的倍数,即原式有解,一个特解为:

{k1=k1(a2a1)/dk2=k2(a2a1)/d

a2a1 不是 d 的倍数,那么原一元线性同余方程组无解,可停止下面的计算。得到特解后,我们可以构造出通解(构造方式见扩展欧几里得算法的笔记):

{k1=k1+t(m2/d)k2=k2t(m1/d),tZ

将解回代

将解出的 k1 回代到 (2) 中第一个方程,得到:

(5)x=a1+m1k1=a1+m1[k1+t(m2/d)]=(a1+m1k1)+(m1m2/d)t=(a1+m1k1)+lcm(m1,m2)t

我们可以发现,(5) 式与 (2) 式的结构非常相似,结构均为:

(6)x=C1+C2m(C1,C2,m)

将方程组合并为一个方程

我们上面的过程,即可看作将 (2) 的两个式子合并为了 (6) 这样的一个式子,(6)(2) 同解。那么我们可以不断重复这个步骤,将式子不断合并。即再将 (6)(S) 中第三个式子合并得到新式子,再将新式子与 (S) 中第四个式子合并……

合并最后的结果便是一个式子,我们假设这个式子为:

(7)x=α+bβ

(7) 式非常简单就能解出结果,而且原方程组 (S)(7) 同解,那么我们解 (7) 这个方程即可得到 (S) 的解。

求解最终合并式

若我们要求得最小的正整数解,(7) 式的解其实就是 αmodβ

模板

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;
}

// 如果中途输出-1,-1则需要立即停止
// first-a second-mod
pair<ll, ll> excrt(pair<ll, ll> a, pair<ll, ll> b)
{
    ll ya, yb;
    ll d = exgcd(a.second, b.second, ya, yb);
    if ((b.first - a.first) % d)
        return {-1, -1};
    ya *= (b.first - a.first) / d;
    ll tmp = b.second / d;
    ya = (ya % tmp + tmp) % tmp;
    a.first += a.second * ya;
    a.second = a.second / d * b.second;
    return a;
}

典型例题

原题链接:https://www.acwing.com/problem/content/206/

注意:

  • 为防止乘法溢出,建议先除后乘。
  • C 语言负数取模会成负数,因此取模需要写成 (αmodβ+β)modβ 这种形式。
  • 程序实现时并没有使用上述过程中的那么多变量,同一个变量被重复利用。
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int MAXN = 30;
int n;

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()
{
    cin >> n;
    LL a1, m1;
    cin >> a1 >> m1;
    for (int i = 2; i <= n; i++)
    {
        LL a2, m2, y1, y2;
        cin >> a2 >> m2;
        LL d = exgcd(a1, a2, y1, y2);
        if ((m2 - m1) % d)
        {
            cout << -1 << endl;
            return 0;
        }
        y1 *= (m2 - m1) / d;
        LL tmp = a2 / d;
        y1 = (y1 % tmp + tmp) % tmp;
        m1 += a1 * y1;
        a1 = abs(a1 / d * a2);
    }
    cout << (m1 % a1 + a1) % a1 << endl;
    return 0;
}