快速幂
快速幂的概念
幂运算 an 即为n
个a
相乘,用普通的\(for\)循环计算复杂度为,\(o(n)\)当非常\(n\)大的时候就不能采取这种方式了。快速幂可以较高效的计算出结果。
快速幂的基本思路是分治:将an变为(a2)n/2,这样仅仅将a
变为a2,运算量就减小了一半,重复上面的操作就可以将复杂度从\(o(n)\)优化为\(o(lg(n))\)了。
#include <iostream>
typedef long long ll;
const int mod = 1e9 + 7;
ll qPow(ll a, ll n) {
if (n == 1) {
return a;
} else {
ll t = qPow(a, n >> 1);
t = t * t % mod;
if (n & 1) {
t = t * a;
}
return t % mod;
}
}
int main() {
ll a = 5;
ll n = 100000000000000000;
std::cout << qPow(a, n) << std::endl;
return 0;
}
上面为递归版本,由于递归层数最多为\(lgn\)所以不会出现爆栈的情况。
但是由于递归会将大量信息存储在堆栈中,再加上不断的寻址,就导致时间和空间上的浪费,下面用迭代的方法进行再优化。
这里用a13作为例子,将a13化为a8a4a1,此时注意到a1a1=a2,a2a2=a4,a4*a4=a8,他们的幂都是2的倍数,只需要不断递推就可以求出这些数。
但是8,4,1这些数字是怎么得到的呢?这里用到了二进制数字进行理解。二进制数字中,每一位的数字是低一位数字的两倍 1, 1310=11012 ,那么一开始设置一个\(x = 1\),从\(1101\)的最低位开始,每次乘\(n\)个2(0<=n<4),当前位为1的时候,\(x\)就是我们要得到的数字之一。
从第0位到第3位:
x = 1 * 20=1,当前位为1,所以得到一个x = 1。
x = 1 * 20 * 2=2,当前位为0,没有得到任何数字。
x = 1 * 20 * 2 * 2=4,当前位为1,所以得到一个x = 4。
x = 1 * 20 * 2 * 2 * 2=8,当前位为1,所以得到一个x = 8。
在程序中用>>
来右移一位,再用&
来判断当前为是否为1.
#include <iostream>
typedef long long ll;
const int mod = 1e9 + 7;
ll qPow(ll a, ll n) {
ll res = 1;
ll bash = a;
while (n) {
if (n & 1) {
res = res * bash % mod;
}
bash = bash * bash % mod;
n >>= 1;
}
return res;
}
int main() {
ll a = 5;
ll n = 100000000000000000;
std::cout << qPow(a, n) << std::endl;
return 0;
}
快速幂的取模
由于幂运算的结果往往都非常大,所以通常情况下都需要进行取模操作。
根据模运算的性质,可以得到a n mod m = (a mod m) n mod m。这一步操作对应着上面代码的11行和13行。