1. 程式人生 > >轉載ACM 快速冪取模演算法詳解

轉載ACM 快速冪取模演算法詳解

快速冪取模的用途:在ACM這類競賽中,可能會遇到指數型的資料取模問題,這個時候如果直接用int或者long long儲存,就

有可能會超出計算機整數的存取範圍,而導致資料出錯。所以我們需要一種方法進行計算。而這種方法就是我們這次要講到

的快速冪取模(簡稱快速冪)。這種演算法在時間和空間上都做了儘可能的優化,所以學會之後,會覺得非常好用。

快速冪取模的思路:快速冪實現的最基本的理論就是我們離散課上或者數論中學過的一條公式推出的引理。

引理:積的取餘等於取餘的積的取餘。還有一條 (a+b)mod c=[(a mod c)+(b mod c)]mod c

再在這條引理的基礎之上,對指數型資料進行拆分以及合併,從而得到我們用的快速冪演算法。

本文我就不用例題講解,直接對快速冪進行解析。畢竟快速冪的演算法相對簡單,而且適用題型較為一致。

快速冪具體分析:對a^b進行分析。

對於當a和b較小是直接用int或者long存是沒有問題的,但是當a和b大到一定程度時,這就不是暴力存就

可以解決的問題了。我們應該怎麼去解決這個問題呢?

在這裡我們需要把注意力放在“大”字上面,正是由於a和b過大才導致的問題。所以我們要想辦法不斷地減

小a和b的規模,所謂逐個擊破。

根據上面的那條引理,我們知道了可以把指數拆開,從這個突破口突破。這裡我們就不難想到這樣一個演算法:

  1. //①a是底數,b是指數,mode是取模數,sum是記錄取模的結果
  2. int sum = 1;
  3. a = a % mode;
  4. for(int i = 1; i <= b; i++)
  5. {
  6. sum = sum * a;
  7. }
  8. sum = sum % c;

這是直接利用的 引理而寫出來的程式碼,這只是單純的降低的a的規模,但是這還達不到我們的要求,所以我們

需要進一步改進演算法。(當然還可以繼續降低啊的規模,即將迴圈中的那句換成sum = (sum * a)%mode)

我們已經實現的降低a的規模,所謂我們要想著怎麼降低b的規模。我們首先看兩個樣例:先看b為偶數的樣例

7^16,mode = 3,我們要怎麼進行拆分?

最基本的拆分是這樣的:7*7*7*7*7*7*7*7*7……7,上面的演算法只是將其變為2*2*2*2*2*2*2……2,那麼怎麼減少b

的規模呢?你應該有一點感覺了吧。就是兩兩合併,將(7^16)變成(49^8),這就降低了b的規模,再利用上面

的演算法降低a的規模,最終會變成1 *1*1*1*1*1*1*1。是不是感覺整個數簡單了很多。

按照這個思路,我們可以多迴圈幾次,從而不斷的降低a和b的規模。

那麼b為奇數怎麼辦呢?

其實也很簡單,我們只要在偶數演算法的基礎之上,每次把多出來的這個數跳出來,預先取模再帶入即可。

從而最終得出快速冪的程式碼:


  1. long long Mode(long long a, long long b, long long mode)
  2. {
  3. long long sum = 1;
  4. a = a % mode;
  5. while (b > 0) {
  6. if (b % 2 == 1) //判斷是否是奇數,是奇數的話將多出來的數事先乘如sum
  7. sum = (sum * a) % mode;
  8. b /= 2;
  9. a = (a * a) % mode;// 不斷的兩兩合併再取模,減小a和b的規模
  10. }
  11. return sum;
  12. }

當然有時候你可能會碰到用&的運算子的程式碼實現,其實和這個大致相同,只不過是用&操作符對b的奇偶性進行判斷而已

&的操作符:二進位制位中,1 & 1 = 1,其餘組合均為0

附上用&實現的程式碼:

  1. long long Mode(long long a, long long b, long long mode)
  2. {
  3. long long sum = 1;
  4. while (b) {
  5. if (b & 1) {
  6. sum = (sum * a) % mode;
  7. b--;
  8. }
  9. b /= 2;
  10. a = a * a % mode;
  11. }
  12. return sum;
  13. }


總結:快速冪雖然是個“小”演算法,但是有需要用到它的時候非常有用,所以不能小看它,只要稍加功夫去理解,快速冪

還是很好學的,當然一切都需要你在題目中得到鍛鍊。