1. 程式人生 > 實用技巧 >中國剩餘定理

中國剩餘定理

“有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。問物幾何?”——《孫子算經》

中國剩餘定理

即求多個同餘方程的解。

舉個栗子。求一個數x,使得:

\[\begin{cases} x≡2(mod 3)\\ x≡3(mod 5)\\ x≡2(mod 11)\end{cases}\]

設M=3511=165, a1=3,a2=5,a3=11,

M1=M/a1=55

M2=M/a2=33

M3=M/a3=15

然後我們再求a的逆元c。(擴充套件歐幾里得)

然後結果就是(a1M1c1 + a2M2c2 + a3M3c3)%M

當然,我們要求的不僅僅是符合3個數的要求。於是我們就可以得到程式碼:

(變數名和以上有出入)

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
    if(!b)d=a,x=1,y=0;
    else{
        exgcd(b,a%b,d,y,x);
        y-=(a/b)*x;
    }
}

ll China(int n,ll *m,ll*a)
{
    ll M=1,d,y,x=0;
    for(int i=0;i<n;i++)M*=m[i];
    for(int i=0;i<n;i++){
        ll w=M/m[i];
        exgcd(m[i],w,d,d,y);
        x=(x+y*w*a[i])%M;
    }
    return (x+M)%M;
}
ll m[15],a[15];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    scanf("%lld%lld",&m[i],&a[i]);
    printf("%lld",China(n,m,a));
    return 0;
}

以上是中國剩餘定理的內容(CRT)

擴充套件中國剩餘定理(exCRT)

簡單來說就是對於每兩個同餘方程,我們可以把它們合併成一個同餘方程,並在每次計入一個新的同餘方程的時候得到新的解。我們理性地思考一下,如:

\[\begin{cases} x≡y_1(mod m_1)\\ x≡y_2(mod m_2)\\ ...\\ x≡y_n(mod m_n) \end{cases}\]

\[M=LCM_{i−1}^{k−1}mi \]

其中\(m_1,m_2,m_3...m_n\)為不一定兩兩互質的整數,求x的最小非負整數解.
假設前n-1項已經求出解為x,那麼對於第n個式子:

\[x+tM≡y_n(mod m_n) \]

M和yn、mn是已知的,我們只需exgcd求出x和t就行了。


或者我們把合併感性理解一下,假設原來只有一個同餘方程

\[x≡y_1(mod m_1)\Rightarrow x+k_1m_1=y_1 \]

我們再加入一個方程

\[x≡y_2(mod m_2)\Rightarrow x+k_2m_2=y_2 \]

兩者相減,得到

\[k_1m_1-k_2m_2=y_1-y_2 \]

再用exgcd解得\(k_1\)\(k_2\)即可。那麼要求x的值回代即可。

那麼對於一個新加入的式子就有:

\[-k_1m_1+y_1+tm_3=k_3m_3+y_3 \]

(k1、m1和y1表示上次的x,用②式表示也可以)發現新的未知數是t和k3,重新用exgcd求解即可。

當然這裡只寫了三個式子。想要更多同餘方程組的解?請多次exgcd。這個思維和上面的思維是等價的。
洛谷P4777

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cctype>
using namespace std;
typedef long long ll;
inline ll read()
{
	bool w=0;ll x=0;char c=getchar();
	while(!isdigit(c))w|=c=='-',c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const ll maxn=1e5+100;
	ll y[maxn],m[maxn],n;
	inline void exgcd(ll a,ll b,ll &x,ll &y,ll &d){
		if(!b)d=a,x=1,y=0;
		else exgcd(b,a%b,y,x,d),y-=x*(a/b);
	}
	ll mul(ll a,ll b,ll mod){
		ll res=0;
		while(b){
			if(b&1)res=(res+a)%mod;
			a=(a+a)%mod;
			b>>=1;
		}
		return res;
	}
	inline ll excrt()
	{
		ll xx,yy,k;
  		ll M=m[1],ans=y[1];
		for(int i=2;i<=n;i++)
    	      {
			ll a=M,b=m[i],c=(y[i]-ans%b+b)%b;//x≡y(mod m),ans為上一次的解。
   			ll gcd;
			exgcd(a,b,xx,yy,gcd);//tM+km=y-x,c是常數項,xx和yy分別表示t和k
			ll bg=b/gcd;//這個是為了exgcd的最後一個步驟,求解出最小非負整數解
   			if(c%gcd!=0) return -1;
			xx=mul(xx,c/gcd,bg);
			ans+=xx*M;
			M*=bg;//M為前k個m的lcm
 			ans=(ans%M+M)%M;
		}
		return (ans%M+M)%M;
	}
	inline void cried()
	{
		n=read();
		for(int i=1;i<=n;i++)m[i]=read(),y[i]=read();
		printf("%lld\n",excrt()); 
	}
}
int main()
{
	star::cried();
	return 0;
}