淺談快速冪/快速乘


淺談快速冪

這篇隨筆簡單講解一下數學問題種快速冪的實現原理及實現。


快速冪的用途

顧名思義,快速冪就是很快速的冪運算,試想當你面對一個問題:求\(a^b\)的時候,你的第一反應是開\(long long\)然后用\(for\)循環一點一點求。那么你就已經會了冪運算的\(O(b)\)算法。按常理來講,這樣的算法已經夠用了,但是遇到一些卡時間的題目的時候還是會\(T\),於是快速冪應運而生。簡單地說,快速冪就是一種復雜度為\(O(logb)\)的求冪運算的算法。


快速冪的實現原理

因為快速冪的時間復雜度是\(O(logb)\)的,所以我們自然而然地想到了二進制及位運算。這是顯然的,我們知道,一個整數可以被拆分成若干個\(2^k\)的和。那么類比一下,對於一個冪運算問題\(a^b\),我們可以把\(b\)二進制分解,成為若干個\(2^k\)的和,那么對應下來就是這些和的冪的乘積。

舉個例子:

求解問題:\(3^{42}\)

第一步,將42二進制拆分:

\[(42)_{10}=(101010)_2=1\times 2^5+0\times 2^4+\cdots+0\times 2^0 \]

那么,\(3^{42}\)就變成了:

\[3^{42}=3^{1×32 + 0×16 + 1×8 + 0×4 + 1×2 + 0×1} \]

所以,我們就有了這樣的一個原理:

在求解\(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;
}


免責聲明!

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



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