1. 程式人生 > >中國剩餘定理(又稱 孫子定理)

中國剩餘定理(又稱 孫子定理)

部落格圖片都飛了,所以看的時候請多多擔待。

中國剩餘定理是數論中的一個關於一元線性同餘方程組的定理,說明了一元線性同餘方程組有解的準則以及求解方法。也稱為孫子定理。

本文大部分使用的內容來自維基百科。

中國剩餘定理說明:

這裡寫圖片描述
題意:給出你n個ai和mi,最後讓求出x的最小值是多少。

假設整數m1, m2, … , mn其中任兩數互質,則對任意的整數:a1, a2, … , an,方程組 (S)有解,並且通解可以用如下方式構造得到:

1.設這裡寫圖片描述是整數m1, m2, … , mn的乘積,並設
這裡寫圖片描述,即 這裡寫圖片描述是除了mi以外的n − 1個整數的乘積。

2.設這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述的數論倒數:這裡寫圖片描述

3.方程組(S)的通解形式為:
這裡寫圖片描述
在模 M的意義下,方程組 (S)只有一個解:這裡寫圖片描述

**注:**對任何這裡寫圖片描述,由於這裡寫圖片描述,所以這裡寫圖片描述說明存在整數 這裡寫圖片描述使得這裡寫圖片描述這樣的這裡寫圖片描述叫做 {\displaystyle M_{i}} M_{i}{\displaystyle m_{i}} m_{i}的數論倒數。

具體證明可以參照wiki 這裡寫連結內容

例子也可以看wiki中的“物不知數”。

以下給出一般情況下也就是整數m1, m2, … , mn其中任兩數互質情況下的模板:

關於擴充套件歐幾里得可以看以前的文章這裡是傳送門哦~

程式碼1:

////m[]任意兩個互質
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>

using namespace std;
#define LL long long
const int N=1e6+10;
const LL Me=1e9+7;
const int inf=0x3f3f3f3f;

int a[N],m[N];

LL exgcd(LL a,int b,LL &x,LL &y)
{
    if(a==0&&b==0)
        return -1;
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x); //回代 注意這裡的與擴充套件gcd的不同
    y-=a/b*x;
    return d;
}



LL CRT(int n)
{
    LL M=1;
    for(int i=0;i<n;i++)
        M*=m[i];
    LL ret=0;
    for(int i=0;i<n;i++)
    {
        LL x,y;
        LL tm=M/m[i];

        int xxx=exgcd(tm,m[i],x,y);
        ret=(ret+tm*x*a[i])%M;
    }
    return (ret+M)%M;
}



int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&m[i],&a[i]);
    }
    cout<<CRT(n)<<endl;

    return 0;
}

拓展:

中國剩餘數定理是適用於n個mi兩兩互質的情況的,如果不互質呢,下面就是一個轉換:
模不兩兩互質的同餘式組可化為模兩兩互質的同餘式組,再用孫子定理直接求解。
84=22×3×7,160=25×5,63=32×7,由推廣的孫子定理可得 這裡寫圖片描述這裡寫圖片描述同解。

注意求解過程中應先檢查同餘式組上是否存在矛盾,存在矛盾的同餘式組無解。
這裡寫圖片描述

證明過程:

這裡寫圖片描述

程式碼2:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>

using namespace std;
#define LL long long
const int N=1e6+10;
const LL Me=1e9+7;
const int inf=0x3f3f3f3f;

int a[N],m[N];

LL exgcd(LL a,int b,LL &x,LL &y)
{
    if(a==0&&b==0)
        return -1;
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x); //回代 注意這裡的與擴充套件gcd的不同
    y-=a/b*x;
    return d;
}



LL CRT(int n)
{
    if(n==1)
    {
        if(m[0]>a[0])
            return a[0];
        else
            return -1;
    }
    LL x,y,d;
    for(int i=1; i<n; i++)
    {
        if(m[i]<=a[i])
            return -1;
        d=exgcd(m[0],m[i],x,y);
        if((a[i]-a[0])%d!=0)  ////不能整除則無解
            return -1;
        LL t=m[i]/d;
        x=((a[i]-a[0])/d*x%t+t)%t; ////第0個與第i個模線性方程的特解
        a[0]=x*m[0]+a[0];
        m[0]=m[0]*m[i]/d;
        a[0]=(a[0]%m[0]+m[0])%m[0];
    }
    return a[0];
}



int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0; i<n; i++)
    {
        scanf("%d%d",&m[i],&a[i]);
    }
    cout<<CRT(n)<<endl;

    return 0;
}