1 快速幂算法
问题1:对于
,你是会选择使用
来计算,还是使用
来计算?
回答:
。显然,计算
要比计算
的代价更低。
同理,任何幂次计算都可以使用上述的二分的方法,使得总的计算代价更低,即
但是,上述的式子只是一个理想状态,即能够被2以及2的更高次幂整除,也就是说
所有的二进制位上都为1。如果遇到其他的
,例如
,就不能简洁地使用上述的方法进行计算。
问题2:如何能够简洁地计算所有的
次幂呢?
回答:使用二进制。
上面的问题其实也可以转换为,如何使用一个更通用的方法,将拆成一系列的数的组合。回想一下,给定一个二进制,如何转换为十进制?例如给定二进制1011,转换成十进制如下所示(括号的下标代表进制数)
那么如果要计算,我们可以根据二进制,将其转换为
让我们观察上式的结果,可以发现展开后的四项,除了第一项,其余的每一项都是前一项的平方,且每一项都对应二进制的一位。
那么,我们就可以从低位到高位,遍历幂的二进制的每一位,只有当该位上是1时,将结果乘上相应的项,并使用一个变量来记录该项,每遍历一位,变量更新为自身的平方。这就是快速幂算法。伪代码如下所示
给定:底数x和幂y
初始化:res=1
while (未遍历完y的所有二进制位)
if (当前遍历的二进制位是否为1)
rse = res * x
x = x * x
遍历下一位
return res
2 C++代码
快速幂算法(要注意这里面还有一个取模运算,防止指数运算溢出,模数取较大的素数即可):
#define mod 1000000007 // 选择一个较大的素数
int fast_pow(int x, int y) {
int res = 1; // 结果初始化为1,如果是矩阵的快速幂,那就初始化为单位矩阵
while (y) { // 判断是否遍历完
if (y & 1) { // 如果幂次当前位上是1
res = (res * x) % mod; // 如果是矩阵的快速幂,需要自己实现一个两个矩阵相乘的函数
}
x = (x * x) % mod; // 将x更新为自己的平方,即x^{n} = x^{2*n},如果是矩阵的快速幂,这里同样使用矩阵相乘的函数
y >>= 1; // 下一次遍历下一位
}
return res;
}
方阵的快速幂算法:
// 计算两个方阵的乘法
vector<vector<int>> dot(vector<vector<int>>& a, vector<vector<int>>& b) {
vector<vector<int>> res(m, vector<int>(m, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < m; k++) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}
// 方阵的快速幂
vector<vector<int>> fast_pow_matrix(vector<vector<int>>& matrix, int y) {
int m = matrix.size();
// 初始化为单位矩阵
vector<vector<int>> res(m, vector<int>(m, 0));
for (int i = 0; i < m; i++) {
res[i][i] = 1;
}
// 逻辑和数的快速幂算法一致
while (y) {
if (y & 1) {
res = dot(res, matrix);
}
matrix = dot(matrix, matrix);
y >>= 1;
}
return res;
}