快速冪 & 快速乘原理講解(模板)

目錄

1 問題描述

2 原因分析

3 解決方法

4 快速冪講解

5 快速乘講解

6 完整代碼

7 References


1 問題描述

我們發現,在int型下使用pow函數求5的三次方,結果爲124。

2 原因分析

pow函數的返回值爲double型,因浮點數長度問題,存在截斷誤差。

3 解決方法

將變量定義爲double型

有沒有更快求冪的方法?

4 快速冪講解

 假設我們要求a^b,按照樸素算法就是把a連乘b次,這樣一來時間複雜度是O(b),即是O(n)級別。但快速冪能做到O(logn)的複雜度。

快速冪:

對於二進制的位運算,我們需要用到 "&" 與 ">>" 運算符,詳見位運算符的應用

先上實現快速冪運算的具體代碼:

long long ksm(long long a, long long b) {
    long long ans = 1, base = a;
    while(b != 0) {
	if(b & 1 != 0) {
	    ans *= base;
	}
	base *= base;
	b >>= 1;
    }
    return ans;
}

其中“b & 1”指取b的二進制數的最末位,如11的二進制數爲1011,第一次循環,取的是最右邊的“1” ,以此類推。

而“b >>= 1”等效於b = b >> 1,即右移1位,刪去最低位。

以a^11爲例

b的二進制數爲1011,二進制從右向左算,但乘出來的順序是(a^{2^{0}})\,*\,(a^{2^{1}})\,*\,(a^{2^{3}}),是從左向右的。我們不斷的讓base\,*=\,base目的是累乘,以便隨時對ans做出貢獻。

要理解base\,*=\,base這一步:因爲base\,*\,base == base^{2},下一步再乘,就是(base^{2}) * (base^{2}) == base^{4},然後同理(base^{4}) * (base^{4}) == base^{8},由此可以做到basebase^{2}base^{4}base^{8}base^{16}base^{32}.......指數正好是 2^{i} 。再看上面的例子,a^{11} = a^{1}\,*\,a^{2}\,*\,a^{8},這三項就可以完美解決了,快速冪就是這樣。

如還有不明白的地方,建議手動模擬代碼的運行過程。

5 快速乘講解

我們知道,在計算機中做加法運算會比乘法快得多(參考模電中的加法器),做乘法運算往往會溢出,即使用long long類型也拯救不了。因此需要尋找一種能高效完成乘法運算且不會溢出的算法,這就是快速乘算法。

快速乘與快速冪原理相似,也是將運算轉換爲二進制處理:

以a * 11爲例:  

a\,*\,11 = a\,*\, $(1011)_{2}$\,=\, a\,*\,(2^{3})\,+\, a\,*\,(2^{1})\,+\, a\,*\,(2^{0})

就是把快速冪中的 * 號改爲+號

long long ksc(long long a, long long b) {
    long long ans = 0;
    while(b != 0) {
	if(b & 1 != 0) {
	    ans += a;
	}
	a += a;
	b >>= 1;
    }
    return ans;
}

此版本的複雜度和快速冪一樣,也是O(logn)。如果需要特別卡常數,可以去了解O(1)版本的快速乘。

6 完整代碼

爲了防止溢出,一般快速冪和快速乘的算法會在mod下運用,下面給出取模運算代碼。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll mod = 1e7;

//a ^ b
ll ksm(ll a, ll b, ll mod) {
    ll ans = 1, base = a;
    while(b != 0) {
	if(b & 1 != 0) {
	    ans = (ans * base) % mod;
	}
	base = (base * base) % mod;
	b >>= 1;
    }
    return ans;
}

//a * b
ll ksc(ll a, ll b, ll mod) {
    ll ans = 0;
    while(b != 0) {
	if(b & 1 != 0) {
	    ans = (ans + a) % mod;
	}
	a = (a + a) % mod;
	b >>= 1;
    }
    return ans;
}

int main() {
    cout << "5 ^ 3 = " << ksm(5, 3, mod) << endl;
    cout << "345352 * 11 = " << ksc(345352, 11, mod) << endl;
    return 0;
}

 運算結果:

7 References

以上,有問題歡迎指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章