這兩個算法能夠說是OI裏數學模塊最重要的基礎了(若是位運算不算數學的話)。html
一.歐幾里得算法(Euclidean Algorithm)算法
模板水題:LOJ P1212 (LOJ真是個好東西啊)ide
在學習一種算法前,我認爲咱們首先應該知道,這種算法是要解決什麼問題的。函數
小學就已經學過了兩個數的最大公約數,而歐幾里得算法就是爲了求出兩個數a、b的最大公約數的,這個最大公約數能夠表示爲gcd(a,b)。學習
歐幾里得算法又稱展轉相除法,這個名字已經揭示了它的主要思想:展轉相除!idea
它的函數代碼只有一行,簡單便捷,複雜度O(log n):spa
int gcd(int a,int b){ return b?gcd(b,a%b):a; }
不要小看這短短的一行代碼,其中蘊含了無盡的人生智慧(/滑稽)code
由於代碼很短,因此算法的過程我就不贅述了,主要就是遞歸,能夠從代碼中看出來。htm
若是趕時間(好比距離noip就差一天了),能夠忽略掉證實只記代碼,可是我認爲證實仍是必要的,證實在這裏:證實blog
二.擴展歐幾里得算法
首先咱們要理解一個定理:
貝祖定理:若存在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; }