整數快速乘法/快速冪+矩陣快速冪
快速乘法通常有兩類應用:一、整數的運算,計算(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 代替)
首先,給出快速乘法的實現:
int qmul(int a, int b){//根據資料範圍可選擇long long
int ans=0;
while(b){
if(b&1)
ans+=a;//按位與完成位數為1的判斷
b>>=1;
a<<=1;//位運算代替/2和*2
}
return ans;
}
快速乘法取模:
int qmul_mod(int a, int b,int mod){//根據資料範圍可選擇long long
int ans=0;
while (b){
if((b&1)
ans=(ans+a)%mod;//這裡需要b%=mod 以及a%=mod
b>>=1;
a<<=1;//位運算代替/2和*2
}
return ans;//
}
接下來是快速冪的實現:
//快速冪 a^n
int qpow(int a, int n){//根據資料範圍可選擇long long
if(a==0)return 0;//這是個坑,校賽被坑過,很多網上的實現都沒寫這一點
int ans=1;
while(n){
if(n&1)
ans*=x;//和快速乘法的區別
x*=x;//區別,同上
n>>=1;
}
return ans;
}
以及含有取模的快速冪:
int qpow_mod(int a, int n,int mode){//根據資料範圍可選擇long long
if(a==0)
return 0;//這是個坑,校賽被坑過,很多網上的實現都沒寫這一點
int ans=1;
while(n){
if(n&1)
ans=(ans*x)%mod;
x=(x*x)%mod;
n>>=1;
}
return ans;
}
矩陣乘法:
int c[N][N];
void multi(int a[][N],int b[][N],int n)
{
memset(c,0,sizeof c);
for(int i=1;i<=n;i++)
for(int k=1;k<=n;k++)
for(int j=1;j<=n;j++)
c[i][j]+=a[i][k]*b[k][j];
}
矩陣快速冪:
就是算A^n;方法很簡單,把快速冪演算法中的乘法改成矩陣的乘法就可以了
const int N=10;
int tmp[N][N];
void multi(int a[][N],int b[][N],int n)
{
memset(tmp,0,sizeof tmp);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
tmp[i][j]+=a[i][k]*b[k][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=tmp[i][j];
}
int res[N][N];
void Pow(int a[][N],int n)
{
memset(res,0,sizeof res);
for(int i=1;i<=n;i++) res[i][i]=1;
while(n)
{
if(n&1)
multi(res,a,n);//res=res*a;複製直接在multi裡面實現了;
multi(a,a,n);//a=a*a
n>>=1;
}
}