標籤:style blog http color for 2014 問題 代碼
求a^b mod c
演算法1.
首先直接地來設計這個演算法:
int ans=1, i;for(i=1;i<=b;i++) ans*=a;ans%=c;
這個演算法的時間複雜度體現在for迴圈中,為O(b).
這個演算法存在著明顯的問題,如果a和b過大,很容易就會溢出。
那麼,我們先來看看第一個改進方案:在講這個方案之前,要先有這樣一個公式:
a^b mod c=(a mod c)^b
引理:
(a *b) mod c =[ ( a mod c )* (b mod c) ] mod c ;
證明: 設 a mod c =d,b mod c= e;
則:a=t*c + d ; b=k*c + e ;
(a*b)mod c = (t*c+d)(t*c+e)
= (tk c^2 + ( te+dk ) *c + d*e) mod c
=de mod c
即積的取餘等於取餘的積的取餘.
(a ^ b)mod c 由上述公式迭代即可得到 ( a mod c)^b.
證明了以上的公式以後,我們可以先讓a關於c取餘,這樣可以大大減少a的大小,於是不用思考的進行了改進:
演算法2:
int ans = 1 , i ; a = a % c; //加上這一句 for ( i = 1;i<=b;i++) ans = ans * a; ans = ans % c;
既然某個因子取餘之後相乘再取餘保持餘數不變,那麼新算得的ans也可以進行取餘,所以得到比較良好的改進版本。
演算法3:
int ans = 1 ,i ; a = a % c;for(int i = 1;i<=b;i++) ans = (ans * a) % c; //這裡再取了一次餘 ans = ans % c;
這個演算法在時間複雜度上沒有改進,仍為O(b),不過已經好很多的,但是在c過大的條件下,還是很有可能逾時,所以,我們推出以下的快速冪演算法。
快速冪取餘依賴於以下公式:
那麼我們可以得到以下演算法:
演算法4:
int ans = 1 ,i ; a = a % c; if (b%2==1) ans = (ans * a) mod c; //如果是奇數,要多求一步, 可以提前算到 ans 中。k = (a*a) % c; //我們取a^2 而不是a for( i = 1;i<=b/2;i++) ans = (ans * k) % c; ans = ans % c;
我們可以看到,我們把時間複雜度變成了O(b/2).
當然,這樣子治標不治本。
但我們可以看到,當我們令k = (a * a) mod c時,狀態已經發生了變化,我們所要求的最終結果即為 k^(b/2) mod c
而不是原來的a^b mod c,所以我們發現這個過程是可以迭代下去的。當然,對於奇數的情形會多出一項a mod c,所以為了完成迭代,當b是奇數時,我們通過 ans = (ans * a) % c;
來彌補多出來的這一項,此時剩餘的部分就可以進行迭代了。
形如上式的迭代下去後,當b=0時,所有的因子都已經相乘,演算法結束。
於是便可以在O(log b)的時間內完成了。
於是,有了最終的演算法:快速冪演算法。
演算法5:快速冪演算法
int ans = 1;a = a % c; while(b>0) { if(b % 2 == 1) ans = (ans * a) % c; b = b/2; a = (a * a) % c; }
將上述的代碼結構化,也就是寫成函數:
long long PowerMod (int a, int b, int c) { int ans = 1; a = a % c; while(b>0) { if(b % 2 = = 1) ans = (ans * a) % c; b = b/2; // b>>=1; a = (a * a) % c; } return ans; }
本演算法的時間複雜度為O(logb),能在幾乎所有的程式設計(競賽)過程中通過,是目前最常用的演算法之一。
以下內容僅供參考:
擴充:有關於快速冪的演算法的推導,還可以從另一個角度來想。
a^b%c 求解這個問題,我們也可以從二進位轉換來考慮:
將10進位的b轉化成2
進位的運算式:
注意此處的an要麼為0,要麼為1,如果為0,那麼這一項就是1,這個對應了上面演算法過程中b是偶數的情況;
為1對應了b是奇數的情況.