题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1939
设
f
1
=
f
2
=
f
3
=
1
,
f
n
=
f
n
−
3
+
f
n
−
1
f_1=f_2=f_3=1,f_n=f_{n-3}+f_{n-1}
f1=f2=f3=1,fn=fn−3+fn−1。求
f
n
f_n
fn。
前言
这篇博客并不是专门来介绍矩阵乘法加速递推的。
但是既然是模板题就提一下吧。
什么是矩阵乘法?
下面是来自度娘的解释:
也就是说,对于两个矩阵
A
A
A和
B
B
B,在满足第一个矩阵的列数=第二个矩阵的行数
时,这两个矩阵就可以相乘。那么假设
A
A
A是
m
×
p
m\times p
m×p的矩阵,
B
B
B是
p
×
n
p\times n
p×n的矩阵,那么他们相乘得到的矩阵
C
C
C就是一个
m
×
n
m\times n
m×n的矩阵。
而且对于矩阵
C
C
C的任意元素
i
j
_{ij}
ij,都等于矩阵A第i行的所有数字分别乘上矩阵B第j列的所有数字之和
。(其实就是上图的公式)
矩阵乘法和递推的关系?
矩阵乘法和递推关系最密切的例子就是斐波那契数列了。↓
矩
阵
乘
法
求
斐
波
那
契
数
列
矩阵乘法求斐波那契数列
矩阵乘法求斐波那契数列
现在看不懂没关系,可以慢慢理解。
我们知道,斐波那契数列有这样的定义:
f
i
=
f
i
−
1
+
f
i
−
2
f_i=f_{i-1}+f_{i-2}
fi=fi−1+fi−2
那么如果我们有一个
2
×
2
2\times 2
2×2的矩阵,其中第一行分别是
f
i
−
1
f_{i-1}
fi−1和
f
i
−
2
f_{i-2}
fi−2。我们的目标是把第一行承上一个矩阵变成
f
i
f_i
fi和
f
i
−
1
f_{i-1}
fi−1。那么应该怎么办呢?
首先,矩阵
A
A
A和矩阵
C
C
C都含有
f
i
−
1
f_{i-1}
fi−1这一项。那么就先从这里下手。
我们知道,矩阵
C
C
C的
f
i
−
1
f_{i-1}
fi−1在第
1
1
1行第
2
2
2列。那么,根据公式,可以得到
C
1
,
2
=
A
1
,
1
×
B
2
,
1
+
A
1
,
2
×
B
2
,
2
C_{1,2}=A_{1,1}\times B_{2,1}+A_{1,2}\times B_{2,2}
C1,2=A1,1×B2,1+A1,2×B2,2
也就是说
f
i
−
1
=
f
i
−
1
×
B
2
,
1
+
f
i
−
2
×
B
2
,
2
f_{i-1}=f_{i-1}\times B_{2,1} +f_{i-2}\times B_{2,2}
fi−1=fi−1×B2,1+fi−2×B2,2
那么很明显,我们可以得到
B
2
,
1
=
1
,
B
2
,
2
=
0
B_{2,1}=1,B_{2,2}=0
B2,1=1,B2,2=0。这样可以保证进行矩阵乘法之后
C
1
,
2
C_{1,2}
C1,2是
f
i
−
1
f_{i-1}
fi−1。
那么现在来看矩阵
C
C
C中的
f
i
f_i
fi。我们要保证的是
C
1
,
1
=
A
1
,
1
×
B
1
,
1
+
A
1
,
2
×
B
2
,
1
C_{1,1}=A_{1,1}\times B_{1,1}+A_{1,2}\times B_{2,1}
C1,1=A1,1×B1,1+A1,2×B2,1
也就是说
f
i
=
f
i
−
1
×
B
1
,
1
+
f
i
−
2
×
B
2
,
1
f_{i}=f_{i-1}\times B_{1,1}+f_{i-2}\times B_{2,1}
fi=fi−1×B1,1+fi−2×B2,1
我们知道,
f
i
=
f
i
−
1
+
f
i
−
2
f_i=f_{i-1}+f_{i-2}
fi=fi−1+fi−2。所以可以得到
B
1
,
1
=
1
,
B
2
,
1
=
1
B_{1,1}=1,B_{2,1}=1
B1,1=1,B2,1=1
那么整个矩阵
B
B
B都被我们求出来了。
得到了
f
i
f_i
fi和
f
i
−
1
f_{i-1}
fi−1后,我们再将它乘一次矩阵
B
B
B,就可以得到
f
i
+
1
f_{i+1}
fi+1和
f
i
f_i
fi,又可以得到
f
i
+
2
f_{i+2}
fi+2和
f
i
+
1
.
.
.
f_{i+1}...
fi+1...
这样就可以得到
f
n
f_n
fn了。
但是!
你以为就结束了?
这样的时间复杂度是
O
(
n
m
2
)
O(nm^2)
O(nm2),其中
n
n
n表示求斐波那契数列的第
n
n
n项,
m
m
m表示矩阵的长宽。还不如递推。而且递推可以得到
1
1
1到
n
n
n的所有斐波那契数,而矩阵乘法只能求第
n
n
n项。
其实还有个地方可以优化。
我们求
f
n
f_n
fn的时候其实是将原矩阵
A
A
A乘了
n
−
1
n-1
n−1次矩阵
B
B
B的。也就是说
目
标
矩
阵
=
A
×
B
n
−
1
目标矩阵=A\times B^{n-1}
目标矩阵=A×Bn−1
看到
n
−
1
n-1
n−1次方想到了什么?
可以用快速幂!
我们用快速幂的思想求出
B
n
−
1
B^{n-1}
Bn−1,然后再乘上一个矩阵
A
A
A即可。
怎么用快速幂?
其实是一个道理。只不过把矩阵
A
A
A乘矩阵
B
B
B换成矩阵
B
B
B乘矩阵
B
B
B就可以了。
那么最终的时间复杂度为
O
(
m
3
l
o
g
n
)
O(m^3logn)
O(m3logn)。还是很优秀的。
代
码
代码
代码
下面进入正题。
思路:
可以发现矩阵
然后就是套模板了。。。
代码:
#include <cstdio>
#include <cstring>
#define MOD 1000000007
#define ll long long
using namespace std;
const ll b[4][4]=
{
{0,0,0,0},
{0,0,0,1},
{0,1,0,0},
{0,0,1,1}
};
int T,n;
ll f[4],a[4][4];
void mul(ll f[4],ll a[4][4])
{
ll c[4];
memset(c,0,sizeof(c));
for (int i=1;i<=3;i++)
for (int j=1;j<=3;j++)
c[i]=(c[i]+f[j]*a[j][i])%MOD;
memcpy(f,c,sizeof(c));
}
void mulself(ll a[4][4])
{
ll c[4][4];
memset(c,0,sizeof(c));
for (int i=1;i<=3;i++)
for (int j=1;j<=3;j++)
for (int k=1;k<=3;k++)
c[i][j]=(c[i][j]+a[i][k]*a[k][j])%MOD;
memcpy(a,c,sizeof(c));
}
void ask(int x)
{
while (x)
{
if (x&1) mul(f,a);
x>>=1;
mulself(a);
}
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
if (n<4)
{
printf("1\n");
continue;
}
memcpy(a,b,sizeof(b));
f[1]=f[2]=f[3]=1;
ask(n-3);
printf("%lld\n",f[3]);
}
return 0;
}