前言
(看此文章请先了解一下什么是矩阵)
矩阵加速是个很神奇的算法,它可以在极端数据下、级短时间内解决复杂问题,而且还很简单。我们从例题入手:
例题一:Fibonacci第n项
Fibonacci数列满足
,求
。(n<=1e9)
这个数据很大,不能常规地用递推求解。那我们不拐弯路,直接开始公式推导吧:
因为(关键递推式),所以可以把
放入一个矩阵乘法(n*m和m*q的矩阵)中。因为是2项乘2项,所以m=2,为了达到有规律的累乘,得保证结果也为n*2的矩阵,所以q=2。因为暂时没有其他要求的数,所以n=1。矩阵为
,相乘后,(1,1)的位置上就是
。而要求到
,则左矩阵得为
,所以(2,2)位置要通过
、
和另两个数的“加乘”运算得到
。显然,
,所以整个乘法为
,可以得到
,再乘一个
又得到
,以此类推。所以
,f2、f1已知,用一个矩阵快速幂就出来了。
(代码自己水)
例题二:TR的数列
TR非常喜欢数学,经常一个人拿出草稿纸研究奇奇怪怪的数学问题,最近,他突然对数列产生了兴趣,他找到一个数列,类似于斐波拉契,即:Tn=1*f1+2*f2+3*f3+……+n*fn (fn为斐波拉契的第n项值),现在TR想请你帮忙求Tn%m的值(1≤n,m≤2^31-1)。
设,则
所以(关键递推式),左矩阵设置为
,然后推出
所以。
明白了吗?最后,还是用矩阵快速幂。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
long long n,mod;
struct matrix{
int n,m;
long long c[105][105];
matrix(){memset(c,0,sizeof(c));}
matrix operator*(const matrix&a){
matrix r;r.n=n,r.m=a.m;
for(int i=1;i<=r.n;i++)
for(int j=1;j<=r.m;j++)
for(int k=1;k<=m;k++)r.c[i][j]=(r.c[i][j]+c[i][k]*a.c[k][j])%mod;
return r;
}
}a,b;
matrix mpow(matrix a,long long b){
matrix res;res.n=res.m=a.n;
for(int i=1;i<=res.n;i++)res.c[i][i]=1;
for(;b;b>>=1){
if(b&1)res=res*a;
a=a*a;
}
return res;
}
int main()
{
scanf("%lld%lld",&n,&mod);
if(n==1){
printf("%lld",1%mod);
putchar('\n');
return 0;
}
a.n=1,a.m=5;
a.c[1][1]=3,a.c[1][2]=a.c[1][4]=a.c[1][5]=1,a.c[1][3]=2;
b.n=b.m=5;
b.c[1][1]=1;
b.c[2][1]=b.c[2][3]=b.c[3][1]=b.c[3][2]=b.c[3][3]=1;
b.c[4][1]=b.c[4][3]=2,b.c[4][5]=1;
b.c[5][1]=b.c[5][3]=b.c[5][4]=b.c[5][5]=1;
a=a*mpow(b,n-2);
printf("%lld",a.c[1][1]%mod);
putchar('\n');
return 0;
}
例题三:【山东省选】递归数列(版本2)
总结
(例题一、二的题解看懂一个就行)矩阵加速做法为先列出关键递推式(右边要为一次m项式),再根据式子列出1*m和m*m的矩阵,后面就简单了。
(update2021.3.13)扩展:广义矩乘
上面的方法看似只能解决由加减号构成的递推式,实际上由取max、取min符号连接的式子也可以用矩阵加速优化,这时就用到了广义的矩阵乘法:
,(这只是其中一种)
可以证明,广义矩阵乘法仍然满足结合律,但是不满足分配率,不过这就够了,因为矩阵加速只需要用到结合律。
由于摆脱了加减号的束缚,使得矩阵加速可以优化许多看似无法优化的题,动态DP就是在广义矩乘基础上建立的算法。