1.快速幂
快速幂也叫二进制取幂,想要快速求出 an的结果就用它来求。
an表示将n个a乘在一起,时间复杂度是O(n) 级别,如果n太大的话这种方法就不适用了。
如果使用快速幂,时间复杂度能达到O(log2n),因为n有⌊log2n⌋ + 1 个二进制位,所以当知道了每一位的值后,只需要通过log2n次乘法就可计算出an。
用快速幂进行计算
例如计算a9,9的二进制是1001, 9 = 1 × 20 + 0 × 21 + 0 × 22 +1 × 23 = 21 + 23 ;
因此 a9 = a(2^1 + 2^3)= a2^1 * a2^3 , 也就是a1 * a8。现在这样就快多了吧,原来9个a连乘要计算8次乘法,现在使用快速幂只要乘两次。
但是像a8这种式子好像不是很好求的样子。。。
long long pow(int a, int b)
{
long long int ans = 1;
while(b)
{
if(b & 1)
{
ans *= a;
}
a *= a;
b >>= 1;
}
return ans;
}
关于代码中累乘 a *= a
a *= a 即 a = a2,再下一步就是a2 * a2 = a4,再下一步就是a4 * a4 = a8… …
这样随着b右移,a也发生变化:a1 -> a2->a4->a8… … a的指数恰好是2x形式,再看a9 = a1 * a8,类似于 a8这样的式子就完美求出来了,当b二进制当前的末位为1的时候,只要把a乘给ans就能更新ans了。
2.矩阵快速幂
两个矩阵相乘
⎝⎛a11a21a31a12a22a32.........a1na2na3n⎠⎞∗⎝⎛b11b21b31b12b22b32.........b1nb2nb3n⎠⎞=⎝⎛c11c21c31c12c22c32.........c1nc2nc3n⎠⎞
举个例子
(acbd)∗(a1c1b1d1)=(a∗a1+b∗c1c∗a1+d∗c1a∗b1+b∗d1c∗b1+d∗d1)
注意:
1.矩阵A*B必须满足 A的列数=B的行数
2.若矩阵A是一个m行s列的矩阵,矩阵B是一个s行n列的矩阵,则C=A*B是一个m行n列的矩阵
矩阵快速幂
设A为矩阵,求An,若直接相乘,需要连乘n-1次,时间复杂度为O(n),
使用矩阵快速幂,时间复杂度为O(log2n)。
那么如何实现矩阵快速幂呢?只要快速幂中的乘法改成矩阵的乘法就行了。
const int N = 2;
int ans[N][N];
int temp[N][N];
int num[N][N];
void multi(int a[][N], int b[][N])
{
memset(temp, 0, sizeof(temp));
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
for(int k = 0; k < N; k++)
temp[i][j] = temp[i][j] + a[i][k] * b[k][j];
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
a[i][j] = temp[i][j];
}
void MatPow(int a[][N], int n)
{
memset(res, 0, sizeof(res));
for(int i = 0; i < N; i++) ans[i][i] = 1;
while(n)
{
if(n&1)
{
multi(ans, a);
}
multi(a, a);
n >>= 1;
}
}
矩阵快速幂主要是用来求一个复杂递推式的某一项问题,只要构造出了关系矩阵,问题就好解决了。
如何构造矩阵
首先说明一下,能用矩阵快速幂优化的递推类型是f[n]=2f[n-1]+3f[n-2]+n3+1这类的,也就是递推要是线性的,且f[n-i]的系数要是常数,可以含有n的多项式。
下面介绍一些递推式:
递推式1.斐波那契数列:Fn=Fn−1+Fn−2,F0=1,F1=2,求Fn
设转换矩阵为A,那么
A∗(Fn−2Fn−1)=(Fn−1Fn)=(Fn−1Fn−1+Fn−2)
之前说过,若矩阵a是一个m行s列的矩阵,矩阵b是一个s行n列的矩阵,则c=a*b是一个m行n列的矩阵
易知,A是一个2×2的矩阵。
设
A=(acbd)
那么
a∗Fn−2+b∗Fn−1=Fn−1,
c∗Fn−2+d∗Fn−1=Fn−1+Fn−2
得a=0,b=1,c=1,d=1
A=(0111)
这样就求出了转换矩阵A
A∗(F1F2)=(F2F3)
A∗(F2F3)=(F3F4)
An−1∗(F1F2)=(FnFn+1)
最后得到的矩阵的第一个元素即为Fn
一些类似的数列:
(1)Fn=Fn−1+Fn−2+1,F1=1,F2=2,求Fn
A∗⎝⎛Fn−2Fn−11⎠⎞=⎝⎛Fn−1Fn1⎠⎞=⎝⎛Fn−1Fn−2+Fn−1+11⎠⎞
解得 A=⎝⎛010111001⎠⎞
求Fn:
An−1∗⎝⎛F1F21⎠⎞=⎝⎛FnFn+11⎠⎞
最后得到的矩阵的第一个元素即为Fn
(2)Fn=Fn−1+Fn−2+n+1,F0=1,F1=2,求Fn
A∗⎝⎜⎜⎛Fn−2Fn−1n1⎠⎟⎟⎞=⎝⎜⎜⎛Fn+1Fnn+11⎠⎟⎟⎞=⎝⎜⎜⎛Fn+1Fn−2+Fn−1+n+1n+11⎠⎟⎟⎞ 解得 A=⎝⎜⎜⎛0100111100110001⎠⎟⎟⎞
求Fn: An−1∗⎝⎜⎜⎛F1F231⎠⎟⎟⎞=⎝⎜⎜⎛FnFn+1n+11⎠⎟⎟⎞
(3)Fn=Fn−2+Fn−1,F1=F2=1,求Fn的前n项和Sn
同理求A:
A∗⎝⎛Fn−2Fn−1Sn−2⎠⎞=⎝⎛Fn−1FnSn−1⎠⎞=⎝⎛Fn−1Fn−2+Fn−1Sn−2+Fn−1⎠⎞
解得 A=⎝⎜⎜⎜⎜⎛0100011011011000001100001⎠⎟⎟⎟⎟⎞
观察上面数各列求出的转换矩阵A,你会发现它们有一些相似性,
一般的,如果有Fn=p∗Fn−2+q∗Fn−1+r∗n+s,可构造矩阵A为:
A=⎝⎜⎜⎜⎜⎛01000pq0rs011000001100001⎠⎟⎟⎟⎟⎞
递推式2:Fn=k∗Fn−1+n2
按照之前的方法写:
A∗(Fn−1n2)=(Fn(n+1)2)=(k∗Fn−1+n2(n+1)2)易知A是2×2的矩阵,不访设 A=(acbd)
得到:
a∗Fn−1+b∗n2=k∗Fn−1+n2 (式子1)
c∗Fn−1+d∗n2=(n+1)2=n2+2n+1(式子2)
解到这里你会发现式子2中有一个关于n的一次项2n和一个常数项,此时求出的abcd的值是不对的,因为将abcd带入原式后计算,是得不到(n+1)2这个式子的,所以不能这样构造。那应该怎么构造呢,在式子2中有一个一次项和一个常数项,那就再构造时把一次项和常数项加入。
A∗⎝⎜⎜⎛Fn−1n2n1⎠⎟⎟⎞=⎝⎜⎜⎛k∗Fn−1+n2(n+1)2n+11⎠⎟⎟⎞
得
A=⎝⎜⎜⎛k000110002100101⎠⎟⎟⎞
递推式3: Fn=k∗Fn−1+n3
同理可求得:
A=⎝⎜⎜⎜⎜⎛k000011000031000321001111⎠⎟⎟⎟⎟⎞
递推式4:Fn=k∗Fn−1+bn
A∗(Fn−1bn)=(Fnbn+1)=(k∗Fn−1+bnbn∗b)
解得:
A=(k01b)
几道模板题:
1.Fibonacci
2.Tr A
3.A Simple Math Problem