快速冪取模算法詳解
1.大數模冪運算的缺陷:
快速冪取模算法的引入是從大數的小數取模的朴素算法的局限性所提出的,在朴素的方法中我們計算一個數比如5^1003%31是非常消耗我們的計算資源的,在整個計算過程中最麻煩的就是我們的5^1003這個過程
缺點1:在我們在之后計算指數的過程中,計算的數字不都拿得增大,非常的占用我們的計算資源(主要是時間,還有空間)
缺點2:我們計算的中間過程數字大的恐怖,我們現有的計算機是沒有辦法記錄這么長的數據的,所以說我們必須要想一個更加高效的方法來解決這個問題
2.快速冪的引入:
我們首先從優化的過程開始一步一步優化我們的模冪算法
1.朴素模冪運算過程:
- #define ans=1
- for(int i=1;i<=b;i++)
- {
- ans*=a;
- }
在這里我們如果要做優化的話,我肥就是每個過程中都加一次模運算,但是我們首先要記住模運算是非常的消耗內存資源的,在計算的次數非常的大的時候,我們是沒有辦法忍受這種時間耗費的
2.快速冪引入:
在講解快速冪取模算法之前,我們先將幾個必備的知識
1.對於取模運算:
- (a*b)%c=(a%c)*(b%c)%c
之后我們來看看快速冪的核心本質
我通過離散課上的學習,將快速冪的本質差不多理解了一下,感覺還是很深刻的
在這里,我們對指數懂了一些手腳,核心思想在於
將大數的冪運算拆解成了相對應的乘法運算,利用上面的式子,始終將我們的運算的數據量控制在c的范圍以下,這樣我們可以客服朴素的算法的缺點二,我們將計算的數據量壓縮了很大一部分,當指數非常大的時候這個優化是更加顯著的,我們用Python來做一個實驗來看看就知道我們優化的效率有多高了
- from time import *
- def orginal_algorithm(a,b,c): #a^b%c
- ans=1
- a=a%c #預處理,防止出現a比c大的情況
- for i in range(b):
- ans=(ans*a)%c
- return ans
- def quick_algorithm(a,b,c):
- a=a%c
- ans=1
- #這里我們不需要考慮b<0,因為分數沒有取模運算
- while b!=0:
- if b&1:
- ans=(ans*a)%c
- b>>=1
- a=(a*a)%c
- return ans
- time=clock()
- a=eval(input("底數:"))
- b=eval(input("指數:"))
- c=eval(input("模:"))
- print("朴素算法結果%d"%(orginal_algorithm(a,b,c)))
- print("朴素算法耗時:%f"%(clock()-time))
- time=clock()
- print("快速冪算法結果%d"%(quick_algorithm(a,b,c)))
- print("快速冪算法耗時:%f"%(clock()-time))
- 底數:5
- 指數:1003
- 模:12
- 朴素算法結果5
- 朴素算法耗時:3.289952
- 快速冪算法結果5
- 快速冪算法耗時:0.006706
- 對於任何一個整數的模冪運算
- a^b%c
- 對於b我們可以拆成二進制的形式
- b=b0+b1*2+b2*2^2+...+bn*2^n
- 這里我們的b0對應的是b二進制的第一位
- 那么我們的a^b運算就可以拆解成
- a^b0*a^b1*2*...*a^(bn*2^n)
- 對於b來說,二進制位不是0就是1,那么對於bx為0的項我們的計算結果是1就不用考慮了,我們真正想要的其實是b的非0二進制位
- 那么假設除去了b的0的二進制位之后我們得到的式子是
- a^(bx*2^x)*...*a(bn*2^n)
- 這里我們再應用我們一開始提到的公式,那么我們的a^b%c運算就可以轉化為
- (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
- 這樣的話,我們就很接近快速冪的本質了
- (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
- 我們會發現令
- A1=(a^(bx*2^x)%c)
- ...
- An=(a^(bn*2^n)%c)
- 這樣的話,An始終是A(n-1)的平方倍(當然加進去了取模勻速那),依次遞推
- int quick(int a,int b,int c)
- {
- int ans=1; //記錄結果
- a=a%c; //預處理,使得a處於c的數據范圍之下
- while(b!=0)
- {
- if(b&1) ans=(ans*a)%c; //如果b的二進制位不是0,那么我們的結果是要參與運算的
- b>>=1; //二進制的移位操作,相當於每次除以2,用二進制看,就是我們不斷的遍歷b的二進制位
- a=(a*a)%c; //不斷的加倍
- }
- return ans;
- }
現在,我們的快速冪已經講完了
我們來大致的推演一下快速冪取模算法的時間復雜度
首先,我們會觀察到,我們每次都是將b的規模縮小了2倍
那么很顯然,原本的朴素的時間復雜度是O(n)
快速冪的時間復雜度就是O(logn)無限接近常熟的時間復雜度無疑逼朴素的時間復雜度優秀很多,在數據量越大的時候,者中優化效果越明顯
3.OJ例題
POJ1995
題意:
快速冪版題
- #include"iostream"
- #include"cstdio"
- #include"cstring"
- #include"cstdlib"
- using namespace std;
- int ans=0;
- int a,b;
- int c;
- int quick(int a,int b,int c)
- {
- int ans=1;
- a=a%c;
- while(b!=0)
- {
- if(b&1) ans=(ans*a)%c;
- b>>=1;
- a=(a*a)%c;
- }
- return ans;
- }
- int main()
- {
- int for_;
- int t;
- scanf("%d",&t);
- while(t--)
- {
- ans=0;
- scanf("%d%d",&c,&for_);
- for(int i=1;i<=for_;i++)
- {
- scanf("%d%d",&a,&b);
- ans=(ans+quick(a,b,c))%c;
- }
- printf("%d\n",ans);
- }
- return 0;
- }