数论 - 中国剩余定理(孙子定理)

一、起源

孙子定理是中国古代求解一次同余式组的方法。是数论中一个重要定理。又称中国余数定理或中国剩余定理。一元线性同余方程组问题最早可见于中国南北朝时期(公元5世纪)的数学著作《孙子算经》卷下第二十六题,叫做“物不知数”问题,原文如下:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?即,一个整数除以三余二,除以五余三,除以七余二,求这个整数。《孙子算经》中首次提到了同余方程组问题,以及以上具体问题的解法,因此在中文数学文献中也会将中国剩余定理称为孙子定理。

二、问题描述

给定 2n 个整数 a1,a2,…,an 和 m1,m2,…,m,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡mi(mod ai)。

输入格式

第 1 行包含整数 n。

第 2…n+1 行:每 i+1 行包含两个整数 ai 和 mi,数之间用空格隔开。

输出格式

输出最小非负整数 x,如果 x 不存在,则输出 −1。

数据范围

1≤ai≤231−1,
0≤mi<ai
1≤n≤25
所有 mi 的最小公倍数在 64 位有符号整数范围内。

输入样例
2
8 7
11 9
输出样例
31

三、算法思路

  • 我们需要求的是一个数x使得:x mod a1 = m1,x mod a2 = m2,x mod a3 = m3,……,x mod an = mn
  • 我们可以先取出两式子,看是否能转化为一个式子,取出x mod a1 = m1,x mod a2 = m2
  • 这两个式子等价于:x = k1a1+m1和x = k2a2+m2
  • 得到:k1a1+m1 = k2a2+m2
  • 得到:k1a1 - k2a2 = m2 - m1 ,这就是上一篇博客《数论 - 欧拉函数、快速幂、扩展欧几里得算法》中所阐述的扩展欧几里得算法(如若不知,可先翻阅上一篇博客),此方程只有当(a1,a2)|(m2-m1)时有解,其中(a1,a2)表示两者的最大公约数
  • 运用扩展欧几里得算法求出一组k1和k2后可得:所有的组解为k1+ka2/d和k2+ka1/d,k为任意整数,要注意还需要翻(m2-m1)/(a1,a2)倍
  • 所以x = k1a1+m1且k1=k1+ka2/d,等价于x = a1k1 + m1 + ka1a2/d
  • 可以令k1 = a1k1 + m1 ,m1 = a1a2/d,则两个式子x = k1a1+m1和x = k2a2+m2化为了一个式子x = k1a1+m1,且结构完全一致,证明可以合并!!!
  • 由此反复合并,最终所有式子都化为一个式子解出x的最小的非负整数解
  • 注意:为了防止溢出,每次都要将k1降到最小正整数解,同样最后的x也要进行此操作得最小的非负整数解。比如:要将a降为mod b的最小正整数,则 a = (a%b + a) %b。

四、代码

#include <iostream>

using namespace std;

typedef long long LL;

//扩展欧几里得算法求裴蜀定理系数x,y
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()
{
    int n;
    cin >> n;

    LL x = 0; //存储最终答案 如果没解则赋值为 -1
    LL a1, m1;
    cin >> a1 >> m1;

    for(int i = 0; i < n - 1; i ++)
    {
        LL a2, m2;
        cin >> a2 >> m2;

        LL k1, k2;
        LL d = exgcd(a1, a2, k1, k2);
        if((m2 - m1) % d) //判断是否无解
        {
            x = -1;
            break;
        }

        k1 *= (m2 - m1) / d; //翻倍
        k1 = (k1 % (a2 / d) + (a2 / d)) % (a2 / d); //保证k1为符合条件的最小值

        m1 = k1 * a1 + m1;
        a1 = abs(a1 / d * a2);
    }

    if(x != -1) x = ((m1 % a1) + a1) % a1; //保证x为符合条件的最小值

    cout << x << endl;

    return 0;
}
  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值