本文章内容大多采集罗老师著作:《算法竞赛入门到进阶》 清华大学出版社, 感谢罗老师的支持。
1. 快速幂概念:
-
快速幂以及扩展的矩阵快速幂,由于场景比较
-
常见,因此竞赛常出现。
问题:高效的计算a的n次幂,这里n很大分析:假如a=10,n=9,这样的情况就连自带高精度的Java也无法处理,一是数字太大,二是计算时间很长。
使用快速幂解法1:先算a²,之后再算(a²)²,巧妙的使用了分治法,时间复杂度为O(㏒₂n) 上代码:
int fastPow(int a,int n){
if(n==1) return a;
int temp=fastPow(a,n/2);//分治方法---这里a在增大,n在减小
if(n%2==1) //奇数个a,这里可以写为 if(n&1)
return temp*temp*a;
else
return temp*temp;
}
方法2:位运算快速幂
- 假如求a¹¹,可以分解成(a²)³、a²、a¹,其幂数分别为:8、2、1,
使用二进制表示它们的和,为1011;其中的0是不存在幂数的,因此要跳过0。这里做个判断即可,1011中的0就是需要跳过的。这个判断,利用二进制运算很容易实现:
(1)n&1,取n的最后一位,并判断这一位是否需要跳过。
(2)n>>1,把n向右移动一位,目的是把刚才处理过的n最后一位去掉。 代码:
int fastPow(int a,int n){
int base=a;//不定义base,直接用a进行计算也可以
int res=1;//用res返回结果
while(n){
if(n&1)//如果n的最后一位是1,表示这个地方需要乘
res*=base;
base*=base;//推算乘积,a²->(a²)²->、、、、
n>>1;//n右移一位,把刚处理过的n的最后一位去掉
}
return res;
}
2. 快速幂取模
- 存在意义:
由于幂运算结果非常大,常常会超出变量类型的最大值,甚至超出内存所能存放的最大数,所以涉及快速幂的题目,通常都要做取模操作,缩小结果。
根据模运算的性质,在快速幂中做取模操作,对aⁿ 取模,和先对a取模,和先对a取模在做模运算的结果是一样的,即:
aⁿ mod m=(a mod m)ⁿ mod m
下面修改位运算pastPow()函数,加上取模操作。以一个残缺程序实验:
if(n&1)
res = (base*res)%mod;
base =(base*base)%mod;
3. 矩阵快速幂:
- 给定一个m×m 的矩阵A,求它的n次幂Aⁿ
,这也是常见的计算。同样有矩阵快算幂的算法,原理是把矩阵当作变量来操作,程序和上面的很相似。
首先需要定义矩阵的结构体,并定义矩阵相乘的操作。注意矩阵相乘也需要取模。
代码:
const int MAXN = 2;//根据题目要求定义矩阵的阶,本例中是2
const int MOD = 1e4;//根据题目要求定义模
struct Matrix{
int m[MAXN][MAXN];
Matrix(){
memset(m,0,sizeof(m));//第一个参数是赋值位置,第二个参数是要赋的值,第三个参数是赋值的个数
}
};
Matrix Multi(Matrix a,Matrix b){ //矩阵的乘法
Matrix res;
for(int i=0;i<MAXN;i++)
for(int j=0;j<MAXN;j++)
for(int k=0;k<MAXN;k++)
res.m[i][j]=(res.m[i][j] + a.m[i][k] * b.m[k][j])%MOD;
return res;
}
下面是矩阵快速幂的程序代码,和前面单变量的快速幂的
代码非常相似
Matrix fastm(Matrix a,int n){
Matrix res;
for(int i=0;i<MANX;i++)
//初始化为单位矩阵,相当于前面程序中的“int res = 1;”
res.m[i][i];
while(n){
if(n&1)
res = Multi(res,a);
a = Multi(a,a);
n>>=1;
}
return res;
}
- 矩阵快速幂的复杂度:上面求Aⁿ,A是m×m的方阵,其中矩阵乘法的复杂度是O(㏒₂n),合起来是O(m³*㏒₂n)。
应用矩阵快速幂的难点在于如何把地推关系转化为矩阵。