這兩個算法可以說是OI里數學模塊最重要的基礎了(如果位運算不算數學的話)。
一.歐幾里得算法(Euclidean Algorithm)
模板水題:LOJ P1212 (LOJ真是個好東西啊)
在學習一種算法前,我認為我們首先應該知道,這種算法是要解決什么問題的。
小學就已經學過了兩個數的最大公約數,而歐幾里得算法就是為了求出兩個數a、b的最大公約數的,這個最大公約數可以表示為gcd(a,b)。
歐幾里得算法又稱輾轉相除法,這個名字已經揭示了它的主要思想:輾轉相除!
它的函數代碼只有一行,簡單便捷,復雜度O(log n):
int gcd(int a,int b){ return b?gcd(b,a%b):a; }
不要小看這短短的一行代碼,其中蘊含了無盡的人生智慧(/滑稽)
因為代碼很短,所以算法的過程我就不贅述了,主要就是遞歸,可以從代碼中看出來。
如果趕時間(比如距離noip就差一天了),可以忽略掉證明只記代碼,但是我認為證明還是必要的,證明在這里:證明
二.擴展歐幾里得算法
首先我們要理解一個定理:
貝祖定理:若存在a、b是整數,則必存在整數x、y,滿足ax+by=gcd(a,b)。
證明在這里寫得比較清楚:證明
需要耐心理解。
貝祖定理
證明:
當 b=0 時,gcd(a,b)=a,此時 x=1 , y=0
當 b!=0 時,
設 ax1+by1=gcd(a,b)=gcd(b,a%b)=bx2+(a%b)y2
又因 a%b=a-a/b*b
則 ax1+by1=bx2+(a-a/b*b)y2
ax1+by1=bx2+ay2-a/b*by2
=ay2+bx2-b*a/b*y2
=ay2+b(x2-a/b*y2)
解得 x1=y2 , y1=x2-a/b*y2
因為當 b=0 時存在 x , y 為最后一組解
而每一組的解可根據后一組得到
所以第一組的解 x , y 必然存在
證畢。
顯然,因為當b=0時,x=1,y=0,這時x和y是已知的,所以我們很容易想到通過遞歸來求解。
不斷返回下一層的解,來得到這一層的解,最終回溯回來,得解。
因為是借助歐幾里得算法進行回溯的,所以復雜度也是O(log n)。
基本理解擴展歐幾里得算法后,我們就可以來看看例題了。
例題:洛谷oj P1082
同余定理:給定一個正整數$m$,如果兩個整數$a$和$b$滿足$a-b$能夠被$m$整除,即$(a-b)/m$得到一個整數,那么就稱整數$a$與$b$對模$m$同余,記作$a\ ≡\ b\ (\ mod \ m \ )$。對模$m$同余是整數的一個等價關系。
其實就是$a\ mod\ m\ =\ b\ mod\ m$。
當$b>=1$時,因為$1\ mod\ b\ =\ 1$,所以$ax\ ≡\ 1(mod b)$就是$ax mod b=1$。其實就差不多是方程$ax\ +by\ =\ 1$,y可能為負數,所以我們在做exgcd后還要加個答案處理。
ac代碼:
#include <cstdio> using namespace std; long long a,b; long long x,y; inline void exgcd(long long a,long long b){ if (b==0){ x=1,y=0; return ; } else exgcd(b,a%b); long long x1=x; x=y,y=x1-a/b*y; return ; } int main(){ scanf("%lld%lld",&a,&b); exgcd(a,b); while (x<0) x+=b; x%=b; printf("%lld",x); return 0; }