快速冪算法可以說是ACM一類競賽中必不可少,並且也是非常基礎的一類算法,鑒於我一直學的比較零散,所以今天用這個帖子總結一下
快速乘法通常有兩類應用:一、整數的運算,計算(a*b) mod c 二、矩陣快速乘法
一、整數運算:(快速乘法、快速冪)
先說明一下基本的數學常識:
(a*b) mod c == ( (a mod c) * (b mod c) ) mod c //這最后一個mod c 是為了保證結果不超過c
對於2進制,2n可用1后接n個0來表示、對於8進制,可用公式 i+3*j == n (其中 0<= i <=2 ),對於16進制,可用 i+4*j==n(0 <= i <=3)來推算,表達形式為2i 后接 j 個0。
接下來讓我們盡可能簡單的描述快速乘法的思想:
a*b
快速乘法的基本思想 ,是二進制和乘法分配律的結合,(不由得想起浮點數不滿足結合律,嚴重吐槽!!!╮(╯-╰)╭),比如說,13 ==(1101)2 ,4*13等於4*(1101)2 ,用分配律展開得到4*13 == 4*(1000+100+1)2,我們不難觀察出,快速冪可以通過判斷當前的位(bit)是1還是0,推斷出是否需要做求和操作,每次移動到下一位(bit)時,就對ans進行*2操作,等待是否求和。由於除以2和位移操作是等效的,因此這也可以看作是二分思想的應用,這種算法將b進行二分從而減少了不必要的運算,時間復雜度是log(n)。
a^b
快速冪其實可以看作是快速乘法的特例,在快速冪中,我們不再對ans進行*2操作,因為在a^b中b的意義已經從乘數變成了指數,但是我們可以仍然把b寫成二進制,舉例說明:此時,我們將4*13改為4^13,13=(1101)2 ,二進制13寫開我們得到(1000+100+1),注意,這里的所有二進制是指數,指數的相加意味着底數相乘,因此有4^13 == 48 * 44 * 41。再注意到指數之間的2倍關系,我們就可以用很少的幾個變量,完成這一算法。這樣,我們就將原本用循環需要O(n)的算法,改進為O(logN)的算法。
按照慣例,給出盡可能簡潔高效的代碼實現 (以下所有int都可用long long 代替)
首先,給出快速乘法的實現:
1 //快速乘法 2 int qmul(int a,int b){// 根據數據范圍可選擇long long 3 int ans=0; 4 while(b){ 5 if( b&1)ans+=a;//按位與完成位數為1的判斷 6 b>>=1;a<<=1;//位運算代替/2和*2 7 } 8 return ans; 9 }
如果涉及到快速乘法取模,則需要進行一些微小改動
改動所基於的數學原理,請參考紅色字體標出的數學常識
1 //快速乘法取模 2 int qmul_mod(int a,int b,int mod){ 3 int ans=0; 4 while(b){ 5 if((b%=mod)&1)ans+=a%=mod;//這里需要b%=mod 以及a%=mod 6 b>>=1;a<<=1; 7 } 8 return ans%mod; //ans也需要對mod取模 9 }
接下來是快速冪的實現:
1 //快速冪 a^b 2 int qpow(int a,int b){ 3 if(a==0)return 0;//這是個坑,校賽被坑過,很多網上的實現都沒寫這一點 4 int ans=1; 5 while(b){ 6 if(b&1)ans*=a;//和快速乘法的區別 7 b>>=1;a*=a;//區別,同上 8 } 9 return ans; 10 }
以及含有取模的快速冪:
int qpow_mod(int a,int b,int mod){ if(a==0)return 0; int ans=1; while(b){ if(b&1)ans=(ans%mod)*(a%mod);//如果確定數據不會爆的話,可寫成 ans*=a%=mod; b>>=1;a*=a%=mod;//等價於a=(a%mod)*(a%mod),且將一個模運算通過賦值代替,提高了效率 } return ans%mod;//數據不會爆的話,這里的%運算會等價於第5中不斷重復的 ans%mod }
如果我們對於性能還有更進一步的要求,那么也就是減少取模運算了,那么我們需要確定數據范圍不會爆掉
在這樣的前提下,我們可以只用原先1/4的取模運算量完成快速冪
int qpow_mod(int a,int b,int mod){ if(!a)return 0; int ans=1; while(b){ if(b&1)ans*=a%=mod;//這里的模運算只有一個 b>>=1;a*=a;//這里的模運算沒有了 } return ans%mod; }
這些天找了好久,終於找到了純粹的整數快速冪題目,按照慣例,給一波傳送門:
poj1995:http://poj.org/problem?id=1995
這個題。。。沒什么好說的,但是需要注意,用1/4模運算量的那種寫法,數據會爆,所以必須寫成完全取模的運算,這樣程序會慢一點。。。嗚嗚嗚,63ms水過,這是目前我做的最慢的了,如果大神知道如何在16ms及以下A掉它,歡迎聯系我謝謝~o(* ̄▽ ̄*)ブ

實現的代碼如下:
1 #include<cstdio> 2 int z,a,b,m,h,sum; 3 int qpow_mod(int a,int b,int mod){ 4 if(!a)return 0; 5 int ans=1; 6 while(b){ 7 if(b&1)ans=ans%mod*(a%=mod); 8 b>>=1;a=a%mod*(a%mod); 9 } 10 return ans%mod; 11 } 12 int main(){ 13 scanf("%d",&z); 14 while(z--){ 15 scanf("%d%d",&m,&h);sum=0; 16 while(h--){ 17 scanf("%d%d",&a,&b); 18 sum+=qpow_mod(a,b,m); 19 sum%=m; 20 } 21 printf("%d\n",sum); 22 } 23 }
先更新到這,有時間再更新矩陣的Strassen算法以及矩陣快速冪,,大家稍后見(●'◡'●)
2016-06-13 16:47:56
大家好,我又回來啦
二、矩陣運算:(快速冪)(Strassen算法有空再說)
矩陣的快速冪運算,其實思路和上面的整數快速冪是一樣的,對指數進行二分,不過我們對於快速冪本身,可能既可以寫成函數,也可以寫成運算符重載,所以這里我寫的是運算符的重載,畢竟重載練得少,得多練一練
首先我們可以定義一個矩陣數據結構,也可以直接用二維數組
1 #define N 100 2 struct matrix{ 3 int m[N][N]; 4 };
然后我們重載^運算符,完成矩陣m的b次冪的快速冪運算
這里為了我自己代碼習慣,我重載了*和*=兩種運算符,當然,在寫的時候跪在了忘了寫函數聲明上,畢竟C++不是java,對於函數聲明的順序有依賴,so~大家記得寫函數聲明呦
代碼如下
1 //矩陣的數據結構 2 struct matrix{ 3 int m[N][N]; 4 }; 5 matrix operator * (matrix ,matrix);//重載聲明 6 matrix operator *= (matrix,matrix); 7 matrix operator ^ (matrix a,int b){ 8 matrix ans; 9 for(int i=0;i<N;i++) 10 for(int j=0;j<N;j++)ans.m[i][j]=(i==j);//初始化為單位矩陣 11 if(b&1)ans*=a; 12 b>>=1;a*=a; 13 return ans; 14 } 15 matrix operator * (const matrix a,const matrix b){//朴素矩陣乘法 16 matrix ans; 17 for(int i=0;i<N;i++) 18 for(int j=0;j<N;j++) 19 for(int k=0;k<N;k++) 20 ans.m[i][j]=a.m[i][k]+b.m[k][j]; 21 return ans; 22 } 23 matrix operator *= (matrix a,const matrix b){ 24 return a=b*b; 25 }
當然,有必要的時候,我會再更新一波Strassen算法,考慮到在很多情況下,Strassen算法反而會降低矩陣運算的速度,所以我們就先到這里~拜拜(●ˇ∀ˇ●)
