都知道算某個數的冪須要線性的複雜度,爲了優化複雜度,就出現了所謂的快速冪。算法
快速冪的代碼很短,可是要原理須要一點心思。學習
首先,咱們知道, 優化
a^b = a^c * a^d (c+d=b)spa
那麼,不就能夠經過 a^b = a^b1 * a^b2 * a^b3... * a^bn (b1 + b2...+bn = b) 來快速得到a^b嗎?這個方法的優越性在於,若是能夠線性的求出a^b1~a^bn,不就是很快的算法嗎?code
由於a^b=a^c*a^d,c+d=b這條原理,咱們的目標是找到廣泛知足 b = b1 + b2 ... + bn的規律。因此,咱們必需要找出一個統一的方法來肯定b對應的b1~bn分別是多少。blog
若是各位知道進制轉換的原理,就能夠知道:一個n進制的數轉爲十進制等於 求和( i = 0~n-1 ) n ^ i * 第 i 位的數字。博客
例如,十進制數10的二進制數1010能夠表示爲: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1 (這裏挺繞的,爲了方便理解和驗證,2^3*1意思是 2進制,第3位,二進制數字1010中第三位是1)數學
仔細一想,這不就是咱們要的"肯定b對應的b1~bn分別是多少"嗎?這也是快速冪的原理所在,將一個質數分解爲許多能夠線性一個個求出的2的次方的冪。class
我再次重申a^b=a^c*a^d,c+d=b這條原理,不能不搞清楚乘法和加法的關係,咱們以前獲得的加法規律其實是應用於c+d=b這裏的,最後的計算仍是要用乘法。變量
以前我一直在說,這個方法或者說規律的優越性在於咱們能夠線性的求出相加的每個指數。
例如,我知道十進制數10,也就是二進制數1010,我就能夠在線性時間複雜度裏獲得 2^0 * 0 、 2^1 * 1 、 2^2 * 0 、 2^3 * 1。
說了這麼多廢話,目的在於接下來的這一條原理。
( a^(2^0) )^2 = a^(2^1) 你能夠親自驗證一下這條神奇的性質。不只是對0+1 = 1有效,你能夠把0換成任何一個數,把1換成那個數+1,看看還會不會生效。
對於每一個數k,概括一下,就是 (a^(2^k) )^2 = a^(2^(k+1) ) 。其實本身稍微一想,就明白是怎麼回事了。
typedef long long ll;
ll poww(ll a, ll b)//a^b { ll re = 1; while(b != 0) { if(b & 1) re *= a; a = a * a; b >>= 1; } return re; }
解釋一下代碼。
b & 1表明着咱們以前的判斷"爲了避免讓它「濫竽充數」地也來分一杯羹,咱們使用&運算符,判斷這個二進制最後一位是否爲0"。爲何要判斷「最後一位」,由於咱們在判斷完指數的二進制的某一位後,那一位再也不有用,
因此咱們使用 b >>= 1也就是位運算符「右移」來消除使用過的那一位。因爲咱們不想計算當前是哪一位,而且但願代碼儘量的簡便高效,咱們只能不計當前是多少位,
用以前說過的利用平方來求下一個b1~bn中的一個。(b=Σ(i=1~n) bi,Σ爲求和,求b1+.2+...bn,我有點囉嗦,但能讓更多人看懂)。
因此,爲了簡潔高效地完成任務,實際上咱們把原來的 一個n進制的數轉爲十進制等於 "求和( i = 0~n-1 ) n ^ i * 第 i 位的數字」 變成了 「 求和( i = 0~n-1 ) n ^ i 」。
例如,十進制數10的二進制數1010按照咱們原來的方式是: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1,而在代碼裏是 2^0 + 2^1 + 2^2 + 2^3
這就不可避免的形成了在某個數的二進制的某一位是0而形成本該是0的指數被咱們計算成了別的數。因此,咱們必定要在一開始加上二進制下最後一位是否爲0的條件,若是不是0那麼就能夠把當前獲得的結果乘到咱們的最終結果變量上,若是是0則不能乘到最終結果變量上,可是a依然要平方,不能由於這一位數字的結果被忽略而不計下一位數字的結果應當按照咱們以前的方法線性求出本當乘上的數字。(例如,按照咱們以前1010的例子,2^0 * 0被咱們忽略,可是若是上一位數字的結果被忽略就不考慮下一位的話,這一位的指數就是2^0*1了,與咱們期待的結果不符。)
說了這麼多,發現本身想說的其實能夠精煉一下,把本身的思考過程部分隱去。可是轉念一想,對於第一次據說線性篩的OIer或者別的學習者須要詳細的描述,而我本身也不能保證一直記住快速冪的原理,權當整理了。
若是本篇博客有差錯或者不恰當之處,請多多指正。