信息學中的數論(一)


做oi題目的時候,遇到數論題會令我興奮不已。

這一篇讓我來聊一聊我學過的gcd,lcm,擴展歐幾里得算法,逆元,組合數等。

這篇貼的代碼都是未經過編譯運行的,所以如果有錯或有疑問請評論。

那么什么是數論

和數學有關的非幾何都是數論?

嘛,我也不知道定義,那么就草率地認為所有和數學有關的非計算幾何知識都是數論吧。

 

我們先來聊一聊gcd。

這個東西,非常的有用。

它的名字叫最大公約數。

正常人都知道,有一個方法叫輾轉相除法(證明略):

int gcd(int a,int b)
{
    if(!b)return a;
    return gcd(b,a%b);
}

我們默認gcd的時間復雜度是log的,你也可以認為是O(1)的,只不過它的最壞時間復雜度是log的。

不過像我這種渣渣貌似不是很會證明,感受一下是這樣的。。

那么gcd講完了。

 

我們來看lcm。

lcm的名字叫最小公倍數.

正常人都知道,gcd(a,b)*lcm(a,b)=a*b

那么只要求出gcd,就可以求出lcm了。

我們來證明一下,

假如a和b的gcd是g,那么設a=A*g,b=B*g,易證gcd(A,B)=1,

所以lcm(a,b)=lcm(A*g,B*g)=A*B*g=a*b/g

所以gcd(a,b)*lcm(a,b)=a*b。

這個東西,或許有用。

 

我們來講一下擴展歐幾里得算法。

假如我們要求一個不定方程ax+by=c的整數解(a,b,c都是非0整數)。

那么,這個不定方程有整數解。當且僅當c能被gcd(a,b)整除,

其必要性很好證明,由於以下解法,我們也能證明它的充分性。

我們假設,gcd(a,b)=g,c=C*g,那么ax+by=C*g

那么我們只要求出ax+by=g的解,就能求出原方程的解了。(在那基礎上*C即可)

以下將說明

我們可以通過bx+(a%b)y=g的解,得到ax+by=g的解。這樣我們就可以通過gcd(a,b)的遞歸過程來順便求得不定方程的解。

假設,bx1+(a%b)y1=g①

ax2+by2=g,

已知x1,y1,求x2,y2。

方程①等價於bx1+(a-[a/b]*b)y1=g  這里[]符號是向下取整

那么bx1+(a-[a/b]*b)y1 = ax2+by2

b(x1-[a/b]*y1-y2) + a*(y1-x2) = 0

x2,y2的一個解就是{x2=y1,y2=x1-[a/b]*y1}

眾所周知,正常的不定方程有無數解,

若x=X,y=Y是方程的一個解的話,

那么該不定方程的解集就是:x=X+kb,y=Y-ka,k*g為整數

於是我們得到以下代碼:

int exgcd(int a,int b,int&x,int&y)
{
    if(!b){x=1,y=0;return a;}
    int x1,y1;
    int g=exgcd(b,a%b,x1,y1);
    x=y1;y=x1-a/b*y1;
    return g;
}

擴展歐幾里得算法,超級有用

 

那么重頭戲來了。逆元,是個非常,非常,非常有用的東西。

幾乎每道計數題,都要用到它。

在講逆元之前,我們先來聊聊取模。

有一個東西叫同余方程,a≡b(mod p)  表示a%p=b%p。

由於中間那個符號太難打,我之后都會用第二種方法來表示啦。

我們來聊聊%的性質。

(a%p+b%p)%p = (a+b)%p

(a%p-b%p)%p = (a-b)%p

(a%p)*(b%p)%p = (a*b)%p 

有一些重要的定理(摘錄自別的博客):

若a≡b (% p),則對於任意的c,都有(a + c) ≡ (b + c) (%p);
若a≡b (% p),則對於任意的c,都有(a * c) ≡ (b * c) (%p);
若a≡b (% p),c≡d (% p),則 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p),
(a * c) ≡ (b * d) (%p),(a / c) ≡ (b / d) (%p); 

還有一個重要的定理(費馬小定理):

當p為質數時,a^(p-1) %p=1。(這個很重要,不會證也得背下來)

還有另一個較完整的定理(歐拉定理)。感興趣的話看 http://blog.csdn.net/qq_24451605/article/details/47045279

 

計數問題中,有很多答案都是非常非常大的,為了避免高精度,精簡答案,許多任務都要求將答案對一個數p取模。

一個最簡單的問題,求n!%p,很簡單吧,都會吧。

那么如果求n!/m!%p呢?(m<=n)。

 

好了是時候來聊一聊逆元了

逆元的定義是這樣的:

假如ax % p=1,

那么最小的正整數解x就是a的逆元。(如果無解的話,那就是沒有逆元了)

那么,當p不相同的話,a的逆元也不同。

所以這個p很重要。

我們來想一下,假設我們要求(s/a) %p的值,

我們設x是a的逆元(%p意義下)

那么ax %p = 1

sax % p = s(ax%p) %p = s%p 

sx % p = s/a % p

所以,我們只要知道a的逆元,就可以求出(s/a) %p的值了。

當p是質數的時候:

因為a^(p-1) %p = 1(費馬小定理),

所以a * a^(p-2) % p = 1

所以a^(p-2)就是a的逆元。

很完美吧。

那么有一種做法,就是當p是質數的時候,快速冪來求逆元。

那么如果p不是質數呢?

我們來想一下,ax % p =1

我們設ax = py +1(x,y均為整數),那么ax%p=1很顯然吧

於是,ax-py = 1,ax + (-p) y =1

是不是很眼熟?求不定方程的整數解,擴展歐幾里得算法!。

於是我們也知道,如果gcd(a,p)不為1的話,a就沒有逆元了。

我們可以通過歐幾里得算法,來求出該不定方程的一個解。

如果要使x為正整數且盡可能的小呢?

通過前面所說的:x=X+kb

我們將x賦為 (b+(x%b))%b 即可。(c++的取模運算會保留負號的)

那么,逆元的一部分就聊完了

 

我們來回顧一下

 

當p為質數,a^(p-2)是a的逆元,通過快速冪可求。

否則,可以用擴展歐幾里得算法,設ax-py = 1,求得不定方程的解再轉化,即可求逆元。

 

通過上面長長的贅述,

我們來聊一下組合數。

定義組合數C(n,m)為n!/m!/(n-m)!,

生活中的意義就是在n個不同的球里面無次序選m個球的方案數。

由於組合數可能非常巨大,

通常要求C(n,m)%p。

即求n!/m!/(n-m)!%p。

如果n<=1000000,m<=1000000

我們發現,除號后面的都是階乘,

我們可以預處理階乘,預處理階乘的逆元,即可快速求組合數。

來思考一下,

當p是個質數的時候,求1000000個階乘的逆元,每次快速冪很慢吧。

那么怎么快速求階乘的逆元呢?

我們來想,假如x!的逆元是a,

那么 x! * a % p =1

於是(x-1)! * (x*a) % p =1

我們就知道了 (x-1)!的逆元 就是 x!的逆元*x%p

這樣,先求出1000000!的逆元,再倒序求階乘逆元,速度就非常快了。。

不過

有一些特殊情況:比如1000! % 300 =0

那么1000!就沒有逆元了。

所以上述方法適用於p是質數且p>n,p>m的情況。

如果p不是質數??如果p<=n或p<=m??

自己去想吧,太晚了我該睡覺了。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM