淺談快速冪
這篇隨筆簡單講解一下數學問題種快速冪的實現原理及實現。
快速冪的用途
顧名思義,快速冪就是很快速的冪運算,試想當你面對一個問題:求\(a^b\)的時候,你的第一反應是開\(long long\)然后用\(for\)循環一點一點求。那么你就已經會了冪運算的\(O(b)\)算法。按常理來講,這樣的算法已經夠用了,但是遇到一些卡時間的題目的時候還是會\(T\),於是快速冪應運而生。簡單地說,快速冪就是一種復雜度為\(O(logb)\)的求冪運算的算法。
快速冪的實現原理
因為快速冪的時間復雜度是\(O(logb)\)的,所以我們自然而然地想到了二進制及位運算。這是顯然的,我們知道,一個整數可以被拆分成若干個\(2^k\)的和。那么類比一下,對於一個冪運算問題\(a^b\),我們可以把\(b\)二進制分解,成為若干個\(2^k\)的和,那么對應下來就是這些和的冪的乘積。
舉個例子:
求解問題:\(3^{42}\)。
第一步,將42二進制拆分:
那么,\(3^{42}\)就變成了:
所以,我們就有了這樣的一個原理:
在求解\(a^b\)的時候,如果\(b\)是奇數,那么原式就是:\(a\times a^{b-1}\)。
同理,如果\(b\)是偶數,那么原式就可以變成:\(a^{b\div2}\times a^{b\div 2}\).
這是一種倍增的思想,這樣我們就把原來的\(O(b)\)算法就被優化成了\(O(logb)\)的算法。
這就是快速冪的實現原理。
快速冪的代碼實現
根據我們剛剛學習的快速冪的實現原理,我們很容易發現,這個東西可以用遞歸來實現。代碼如下:
int qpow(int a,int b)
{
if(!b)
return 1;
else if(b&1)
return a*qpow(a,b-1);
else
{
int t=qpow(a,b>>1);
return t*t;
}
}
但是,學過遞歸的小伙伴應該知道,遞歸的常數巨大無比。所以上面的代碼並不是一個資深\(OIer\)會選擇的東西。
那快速冪怎么寫保證常數不大呢?
我們采用一種迭代的寫法:
我們會發現,無論\(b\)為何值,它在快速冪迭代的過程中要么\(-1\),要么\(\div 2\)。但無論它采用了以上的哪一種操作,都必會有一個時刻,\(b=1\)。,也就是說,\(b\)在迭代的過程中,至少會有一個時刻\(b\)為奇數。
那么我們考慮,我們完全可以在\(b\div 2\)的迭代中,先不使迭代的結果影響到答案,而是先把迭代的結果儲存下來,然后等到\(b\)為奇數的時候統一加到答案里去。這樣就省去了繁瑣的遞歸和記錄答案的過程,保證了常數小,而且維護了答案的正確性。
代碼如下:
int qpow(int a,int b)
{
int ret=1;
while(b>0)
{
if(b&1)
ret*=a;
a*=a;
b>>=1;
}
}
return ret;
}
快速乘的原理及其代碼實現
其實,就是把快速冪的乘法運算變成了加法運算。
原理超級容易理解...
模板也大同小異:
ll qmult(ll a,ll b)
{
ll ret=0;
while(b>0)
{
if(b&1)
ret=(ret+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return ret;
}
