概述
矩阵快速幂求线性常系数递推
这个东西十分好用,但是搞懂这个需要先知道两个要点
——矩阵乘法
——常系数递推
矩阵乘法
我们有两个矩阵
然后我们定义一个新矩阵 Z , Z=X×Y Z = X × Y
其中X为a行b列,Y必须为b行c列。
那么对于矩阵中每一个元素 Zij=∑bk=1Xik×Ykj Z i j = ∑ k = 1 b X i k × Y k j
而Z本身就是一个a*c的矩阵。
于是我们就做完了一次矩阵乘法。
目前貌似算矩阵乘法只能枚举i,j,k,时间复杂度为 O(n3) O ( n 3 ) ,是很高的,但是解决常系数递推问题是足够的。
线性常系数递推
设 f(n)=a1f(n−1)+a2f(n−2)+a3f(n−3)+⋯+anf(1)+k f ( n ) = a 1 f ( n − 1 ) + a 2 f ( n − 2 ) + a 3 f ( n − 3 ) + ⋯ + a n f ( 1 ) + k
则这样的可以由这样的式子转移过来的f(n),我们就称为它满足线性常系数递推关系。
这种式子很常见,比如斐波那契数列就是这样的式子。
斐波那契数列: f(n)=f(n−1)+f(n−2) f ( n ) = f ( n − 1 ) + f ( n − 2 )
如果给出一个题,求斐波那契的第N项,那么我们非常自然的想到 O(N) O ( N ) 递推过去。但如果数据范围 N<=1018 N <= 10 18 ,显然 O(N) O ( N ) 的方法是不行的。此时,利用矩阵乘法,我们可以做到 O(log2(N)) O ( log 2 ( N ) ) 。
如何解决?
我们考虑将斐波那契数列转为1个1*2的矩阵。
我们命名为fib
然后我们再定义一个2*2的矩阵
然后我们定义一个ans矩阵
对fib和transfer做一下矩阵乘法,就会发现
于是 ans11 a n s 1 1 就是答案。
有人可能会说,这比线性递推慢很多啊,这样算一次得进行4次运算,但是线性递推1次就能出结果
那么我们再来想,如果要再求一遍,是不是只需要让ans再乘一遍transfer就可以了,那么我们求n项,就乘n次transfer,根据乘法结合律,我们可以先把n个transfer相乘算出来,这个时候用快速幂,就能优化到 O(log2(N)) O ( log 2 ( N ) ) 。这就是用处。
那么我们先来看一段整数快速幂运算
int ksm(int x,int y){//求x的y次方
int res=1;
while(y){
if(y&1){
res*=x;
}
x*=x;
y>>=1;
}
return res;
}
我们可以看到,返回的答案res,在一开始的是时候赋值为1,因为任何数乘以1都等于它本身,这样就不会修改值了。
那么矩阵快速幂,返回的答案为ans,它在一开始的时候怎么赋值呢?显然不能全是1,参考上面的矩乘定义。
其实这里是有通法的,我们定义一个矩阵unit,如果任意矩阵乘以unit都等于它本身,则我们将unit称之为单位矩阵
简单来说,就是一个除主对角线是1以外都是0的矩阵。
至于为什么是这样大家可以自己手推一下。
因为如果有一个矩阵与这个矩阵相乘,肯定是单位矩阵的第一行乘以这个矩阵的一列,无论这个矩阵这一列是谁,都只会保留第一个数,因为第一行第二个往后的数都是0,这个矩阵的第一列第二个往后的数乘上它们都得0,对答案是没有影响的。接下来同理。
所以我们就得到了一个单位矩阵。
接下来就是代码部分:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const long long mod=1e9+7;
struct mat{
int m[2][2];
};
mat zhuanyi={
1,1,
1,0
};
mat danwei={
1,0,
0,1
};
mat mat_mul(mat a,mat b){
mat c;
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
c.m[i][j]=0;
for(int k=0;k<2;k++){
c.m[i][j]+=(a.m[i][k]*b.m[k][j])%mod;
}
c.m[i][j]%=mod;
}
}
return c;
}
mat mat_pow(int k){
mat ans,p;
ans=danwei;
p=zhuanyi;
while(k){
if(k&1){
ans=mat_mul(ans,p);
}
p=mat_mul(p,p);
k>>=1;
}
return ans;
}
int main(){
int n;
cin>>n;
mat ans=mat_pow(n-1);
cout<<ans.m[0][0]<<endl;
return 0;
}
我们将矩阵直接定义为结构体,这样在返回和调用的时候就不需要指针操作,非常便捷。
另外这一题由于递推式的项数只有2,所以矩乘的复杂度为8,是个常数级别,就可以直接忽略了。但是对于项数超多的递推式,就要斟酌一下线性还是矩乘了。
mat_pow是矩阵快速幂,可以看到,基本上和一般快速幂没有区别,只是乘法改为调用上面的mat_mul矩乘函数。
那么最后还有一个问题,就是如何推出一个线性常系数递推关系式的转移矩阵?
也是有通法的。对于一个线性常系数齐次递推
f(n)=a1×f(n−1)+a2×f(n−2)+a3×f(n−3)+⋯+ak×f(n−k)
f
(
n
)
=
a
1
×
f
(
n
−
1
)
+
a
2
×
f
(
n
−
2
)
+
a
3
×
f
(
n
−
3
)
+
⋯
+
a
k
×
f
(
n
−
k
)
,有如下转移矩阵:
至于为何是这样,还是可以手玩一下自己算一算,用中文很难讲的比较明白。
而另一个数列也就显而易见了
对于一个线性常系数非齐次递推
f(n)=a1×f(n−1)+a2×f(n−2)+a3×f(n−3)+⋯+ak×f(n−k)+b
f
(
n
)
=
a
1
×
f
(
n
−
1
)
+
a
2
×
f
(
n
−
2
)
+
a
3
×
f
(
n
−
3
)
+
⋯
+
a
k
×
f
(
n
−
k
)
+
b
,有如下转移矩阵:
另一个数列为
这就是矩阵快速幂,能够解决线性常系数递推的问题
By Chentlh
最后
以上就是生动毛衣为你收集整理的矩阵快速幂求线性常系数递推矩阵快速幂求线性常系数递推的全部内容,希望文章能够帮你解决矩阵快速幂求线性常系数递推矩阵快速幂求线性常系数递推所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复