中國剩餘定理(互質與不互質的情況)
前言:這個東西聽說好久了,一直想學但是總是看到一半就放棄了,今天咬咬牙,就去研究一下吧。
中國剩餘定理:
問題引入
在《孫子算經》中有這樣一個問題:“今有物不知其數,三三數之剩二(除以3餘2),五五數之剩三(除以5餘3),七七數之剩二(除以7餘2),問物幾何?”這個問題稱為“孫子問題”,該問題的一般解法國際上稱為“中國剩餘定理”。具體解法分三步:
- 找出三個數:從3和5的公倍數中找出被7除餘1的最小數15,從3和7的公倍數中找出被5除餘1 的最小數21,最後從5和7的公倍數中找出除3餘1的最小數70。
- 用15乘以2(2為最終結果除以7的餘數),用21乘以3(3為最終結果除以5的餘數),同理,用70乘以2(2為最終結果除以3的餘數),然後把三個乘積相加(15*2+21*3+70*2)得到和233。
- 用233除以3,5,7三個數的最小公倍數105,得到餘數23,即233%105=23。這個餘數23就是符合條件的最小數。
就這麼簡單。我們在感嘆神奇的同時不禁想知道古人是如何想到這個方法的,有什麼基本的數學依據嗎?
我們將“孫子問題”拆分成幾個簡單的小問題,從零開始,試圖揣測古人是如何推匯出這個解法的。
具體推導
首先,我們假設n1是滿足除以3餘2的一個數,比如2,5,8等等,也就是滿足3*k+2(k>=0)的一個任意數。同樣,我們假設n2是滿足除以5餘3的一個數,n3是滿足除以7餘2的一個數。
有了前面的假設,我們先從n1這個角度出發,已知n1滿足除以3餘2,能不能使得 n1+n2 的和仍然滿足除以3餘2?進而使得n1+n2+n3的和仍然滿足除以3餘2?
這就牽涉到一個最基本數學定理,如果有a%b=c,則有(a+kb)%b=c(k為非零整數),換句話說,如果一個除法運算的餘數為c,那麼被除數與k倍的除數相加(或相減)的和(差)再與除數相除,餘數不變。這個是很好證明的。
以此定理為依據,如果n2是3的倍數,n1+n2就依然滿足除以3餘2。同理,如果n3也是3的倍數,那麼n1+n2+n3的和就滿足除以3餘2。這是從n1的角度考慮的,再從n2,n3的角度出發,我們可推匯出以下三點:
- 為使n1+n2+n3的和滿足除以3餘2,n2和n3必須是3的倍數。
- 為使n1+n2+n3的和滿足除以5餘3,n1和n3必須是5的倍數。
- 為使n1+n2+n3的和滿足除以7餘2,n1和n2必須是7的倍數。
因此,為使n1+n2+n3的和作為“孫子問題”的一個最終解,需滿足:
- n1除以3餘2,且是5和7的公倍數。
- n2除以5餘3,且是3和7的公倍數。
- n3除以7餘2,且是3和5的公倍數。
所以,孫子問題解法的本質是從5和7的公倍數中找一個除以3餘2的數n1,從3和7的公倍數中找一個除以5餘3的數n2,從3和5的公倍數中找一個除以7餘2的數n3,再將三個數相加得到解。在求n1,n2,n3時又用了一個小技巧,以n1為例,並非從5和7的公倍數中直接找一個除以3餘2的數,而是先找一個除以3餘1的數,再乘以2。
這裡又有一個數學公式,如果a%b=c,那麼(a*k)%b=a%b+a%b+…+a%b=c+c+…+c=kc(k>0),也就是說,如果一個除法的餘數為c,那麼被除數的k倍與除數相除的餘數為kc。展開式中已證明。
最後,我們還要清楚一點,n1+n2+n3只是問題的一個解,並不是最小的解。如何得到最小解?我們只需要從中最大限度的減掉掉3,5,7的公倍數105即可。道理就是前面講過的定理“如果a%b=c,則有(a-kb)%b=c”。所以(n1+n2+n3)%105就是最終的最小解。
總結
經過分析發現,中國剩餘定理的孫子解法並沒有什麼高深的技巧,就是以下兩個基本數學定理的靈活運用:
- 如果 a%b=c , 則有 (a+kb)%b=c (k為非零整數)。
- 如果 a%b=c,那麼 (a*k)%b=kc (k為大於零的整數)。
心得:又看了一會中國剩餘定理,感覺明白了,其實就是一個轉化的思想,想想問題如果很小你會怎麼解決,再推倒到大的問題上去。好的貼一下,在mod互質的情況下的程式碼 程式碼:
//簡單點說,中國剩餘定理就是一種同餘問題,如果實在理解不了,網上找道小學奧賽題模擬下。
//mod互質的中國剩餘定理
#include <iostream>
using namespace std;
const int maxn=1000;
int a[maxn],m[maxn];//a為餘數陣列,m是mod的陣列
int exgcd(int a,int b,int &x,int &y)
{
if(b==0){
x=1,y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int temp=x;
x=y;
y=temp-a/b*y;
return ans;
}
int china(int n)
{
int M=1;
int i,mi,x0,y0,d,ans=0;
for(i=0;i<n;i++)
M*=m[i];//m陣列存的是模,這些數字兩兩互質,所以直接相乘得出來的數字便是最小公倍數
for(i=0;i<n;i++){
mi=M/m[i];//根據中國剩餘定理的性質
exgcd(mi,m[i],x0,y0);//eg:33*28*a%23=1,因為互質,所以gcd==1,所以利用擴充套件歐幾里得求出來的x0便是a
ans=(ans+mi*x0*a[i])%M;//根據同餘的性質,最後的結果就是 餘數*mod*mi的加和
}
if(ans<0) ans+=M;
return ans;
}
int main()
{
}
mod不互質的情況 題目很多是不互質的情況,我去網上搜到了一幅圖,介紹不互質的情況下的中國剩餘定理。
就是一個合併的過程,跟我之前看到的不太一樣,算了整理下來吧 程式碼:
//有時候可能會用到lcm來求解多租借,因為你求出來的是最小解,所以你就可以通過不斷加lcm求解多組解
int lcm;
int china2(int num){//不互質的中國剩餘定理
int m1=m[0],a1=a[0],m2,a2,k1,k2,x0,gcd,c;
lcm=m[0];
for(int i=1;i<num;i++){
m2=m[i],a2=a[i];
c=a2-a1;
gcd=exgcd(m1,m2,k1,k2);//解得:n1*k1+n2*k2=gcd(n1,n2)
lcm=lcm*m[i]/Gcd(lcm,m[i]);//通過這個迴圈求解出所有mod的最大公約數
if(c%gcd){
flag=1;//!!!!!!china也可以求解出為0的值,所以為0不一定就是無解,所以你要通過flag來判斷是否無解。
return 0;//無解
}
x0=c/gcd*k1;//n1*x0+n2*(c/gcd*k2)=c PS:k1/gcd*c錯誤!
int t=m2/gcd;
x0=(x0%t+t)%t;//求n1*x0+n2*y=c的x0的最小解
a1+=m1*x0;
m1=m2/gcd*m1;
}
return a1;
}