例題:https://www.luogu.org/problemnew/show/P1226
反覆平方法板子(加取餘)
#include<iostream>
using namespace std;
typedef long long ll;
ll poww(ll a, ll b,ll n)
{
ll d=1,t=a;
while(b>0)
{
if(b%2==1) d=d*t%n;//b爲奇數時
b=b/2;
t=t*t%n;
}
return d;
}
int main()
{
ll a,b,c;
cin>>a>>b>>c;
printf("%lld^%lld mod %lld=%lld",a,b,c,poww(a,b,c));
}
快速冪
快速冪的目的就是做到快速求冪,假設我們要求a^b,按照樸素算法就是把a連乘b次,這樣一來時間複雜度是O(b)也即是O(n)級別,快速冪能做到O(logn),快了好多好多。它的原理如下:
假設我們要求a^b,那麼其實b是可以拆成二進制的,該二進制數第i位的權爲2^(i-1),例如當b==11時
a^11=a(2^0+2^1+2^3)
11的二進制是1011,11 = 2³×1 + 2²×0 + 2¹×1 + 2º×1,因此,我們將a¹¹轉化爲算 a2^0*a2^1*a2^3,也就是a1*a2*a8 ,看出來快的多了吧原來算11次,現在算三次,但是這三項貌似不好求的樣子….不急,下面會有詳細解釋。 由於是二進制,很自然地想到用位運算這個強大的工具:&和>> &運算通常用於二進制取位操作,例如一個數 & 1 的結果就是取二進制的最末位。還可以判斷奇偶x&1==0爲偶,x&1==1爲奇。 >>運算比較單純,二進制去掉最後一位,不多說了,先放代碼
int poww(int a, int b) {
int ans = 1, base = a;
while (b != 0) {
if (b & 1 != 0)
ans *= base;
base *= base;
b >>= 1;
}
return ans;
}
以b==11爲例,b=>1011,二進制從右向左算,但乘出來的順序是 a^2^0×a^2^1=a^2^3,,是從左向右的。我們不斷的讓base*=base目的即是累乘,以便隨時對ans做出貢獻。
其中要理解base*=base這一步:因爲 ,下一步再乘,就是 ,然後同理 ,,由此可以做到base–>base2–>base4–>base8–>base16–>base32…….指數正是 2^i ,再看上面的例子,a¹¹= a1*a2*a8,這三項就可以完美解決了,快速冪就是這樣。
由於指數函數是爆炸增長的函數,所以很有可能會超過int的範圍,根據題意選擇 long long還是mod某個數自己決定。
1. 遞歸寫法
LL pow_mod(LL a, LL b,int MOD)//a的b次方
{
if(b == 0)
return 1;
LL ret = pow_mod(a, b/2);
ret = ret * ret % MOD;
if(b % 2 == 1)
ret = ret * a % MOD;
return ret;
}
2. 遞推寫法
LL pow_mod(LL a, LL b,int MOD){//a的b次方
LL ret = 1;
while(b != 0)
{
if(b &1)
{
ret = (ret * a) % MOD ;
}
a = (a * a ) % MOD ;
b>>=1 //b/= 2;
}
return ret;
}
3. 二進制寫法(劃重點!)
LL pow_mod(LL a, LL b, LL MOD){//a的b次方求餘MOD
LL ret = 1;
while(b){
if(b & 1) ret = (ret * a) % MOD;
a = (a * a) % MOD;
b >>= 1;
}
return ret;
}