前置知识
矩阵
矩阵是一个 n × m n \times m n×m的阵列,下面是一个 3 × 3 3\times 3 3×3的矩阵:
[ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} 1&0&0\\\\ 0&1&0\\\\ 0&0&1\\ \end{bmatrix} ⎣⎢⎢⎢⎢⎡100010001⎦⎥⎥⎥⎥⎤
矩阵乘法
若矩阵A的大小为 n × m n\times m n×m ,另一矩阵B的大小为 m × p m\times p m×p,则两个矩阵可以做乘法,得到的矩阵C大小为 n ∗ p n*p n∗p。具体的乘法规则如下:
矩阵A:
[
a
11
a
12
a
13
a
21
a
22
a
23
]
\begin{bmatrix} a_{11}&a_{12}&a_{13}\\\\ a_{21}&a_{22}&a_{23}\\ \end{bmatrix}
⎣⎡a11a21a12a22a13a23⎦⎤
矩阵B:
[
b
11
b
12
b
21
b
22
b
31
b
32
]
\begin{bmatrix} b_{11}&b_{12}\\\\ b_{21}&b_{22}\\\\ b_{31}&b_{32}\\ \end{bmatrix}
⎣⎢⎢⎢⎢⎡b11b21b31b12b22b32⎦⎥⎥⎥⎥⎤
相乘得到矩阵C:
[
a
11
×
b
11
+
a
12
×
b
21
+
a
13
×
b
31
a
11
×
b
12
+
a
12
×
b
22
+
a
13
×
b
32
a
21
×
b
11
+
a
22
×
b
21
+
a
23
×
b
31
a
21
×
b
12
+
a
22
×
b
22
+
a
23
×
b
32
]
\begin{bmatrix} a_{11}\times b_{11}+a_{12}\times b_{21}+a_{13}\times b_{31} &a_{11}\times b_{12}+a_{12}\times b_{22}+a_{13}\times b_{32}\\\\ a_{21}\times b_{11}+a_{22}\times b_{21}+a_{23}\times b_{31}&a_{21}\times b_{12}+a_{22}\times b_{22}+a_{23}\times b_{32} \end{bmatrix}
⎣⎡a11×b11+a12×b21+a13×b31a21×b11+a22×b21+a23×b31a11×b12+a12×b22+a13×b32a21×b12+a22×b22+a23×b32⎦⎤
C++代码实现矩阵乘法:
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
C[i][j] += A[i][k] * B[k][j];
性质:
-
矩阵乘法不满足交换律,但是满足结合律。即 ( A B ) C = A ( B C ) (AB)C=A(BC) (AB)C=A(BC)。
-
单位矩阵:单位矩阵起着特殊的作用,如同数的乘法中的1,这种矩阵被称为单位矩阵。它是个方阵,从左上角到右下角的对角线(称为主对角线)上的元素均为1。除此以外全都为0。根据单位矩阵的特点,任何矩阵与单位矩阵相乘都等于本身。
-
零矩阵:零矩阵即所有元素皆为0的矩阵。
矩阵快速幂
类似于快速幂用于加速乘法,矩阵快速幂用于加速矩阵乘法。我们可以将 O ( n ) O(n) O(n)的乘法优化到 O ( l o g n ) O(logn) O(logn)。
可以对比一下快速幂与矩阵快速幂:
long long quick_pow(long long a, long long b)//快速幂
{
long long res = 1; //1
while (b)
{
if (b & 1)
res = res * a;
b /= 2;
a = a * a;
}
return res;
}
Matrix quick_multi(Matrix a, int t) //矩阵快速幂
{
Matrix res; //单位矩阵
for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)
res.M[i][j] = (i == j);
while (t)
{
if (t & 1)
res = multi(res, a);
t /= 2;
a = multi(a, a);
}
return res;
}
应用
如果我们能够将一个递推式表示成矩阵乘法的形式,那么就可以用矩阵快速幂加速。例如经典的矩阵快速幂加速斐波那契数列。
我们记斐波那契第 i i i项为 F i F_i Fi,可以将 F i = F i − 1 + F i − 2 F_i=F_{i-1}+F_{i-2} Fi=Fi−1+Fi−2写成矩阵乘法的形式:
[ F n F n − 1 ] = [ F n − 1 F n − 2 ] × [ 1 1 1 0 ] \begin{bmatrix} F_{n}&F_{n-1}\\ \end{bmatrix}=\begin{bmatrix} F_{n-1}&F_{n-2}\\ \end{bmatrix}\times \begin{bmatrix} 1&1\\ 1&0\\ \end{bmatrix} [FnFn−1]=[Fn−1Fn−2]×[1110]
更一般的,可以写作
[ F n F n − 1 ] = [ F 2 F 1 ] × [ 1 1 1 0 ] n − 2 \begin{bmatrix} F_{n}&F_{n-1}\\ \end{bmatrix}=\begin{bmatrix} F_{2}&F_{1}\\ \end{bmatrix}\times \begin{bmatrix} 1&1\\ 1&0\\ \end{bmatrix}^{n-2} [FnFn−1]=[F2F1]×[1110]n−2
这样就可以用 O ( l o g n ) O(logn) O(logn)的时间复杂度得到 F n F_n Fn
套路
如果已经得到了矩阵乘法,那矩阵快速幂就是一个模板,一般的矩阵快速幂题目难点在于写出递推式以及将递推式转化成矩阵乘法。递推式因题而异,而将递推式转换为矩阵乘法有着一个比较常用的套路,例如要将
F
n
=
F
n
−
1
+
n
3
+
n
2
+
1
F_n=F_{n-1}+n^3+n^2+1
Fn=Fn−1+n3+n2+1转化为矩阵乘法。
我们先写出
F
n
F_n
Fn以及
F
n
−
1
F_{n-1}
Fn−1的表达式:
F
n
=
F
n
−
1
+
n
3
+
n
2
+
1
F_n=F_{n-1}+n^3+n^2+1
Fn=Fn−1+n3+n2+1
F
n
−
1
=
F
n
−
2
+
(
n
−
1
)
3
+
(
n
−
1
)
2
+
1
F_{n-1}=F_{n-2}+(n-1)^3+(n-1)^2+1
Fn−1=Fn−2+(n−1)3+(n−1)2+1
第一列一般就是 F n F_n Fn的递推式,最后一列是 F n − 1 F_{n-1} Fn−1的递推式,这样才能从 F n − 1 F_{n-1} Fn−1递推到 F n F_{n} Fn:
[ F n F n − 1 n 3 n 2 1 ] = [ ] × [ F n − 1 F n − 2 ( n − 1 ) 3 ( n − 1 ) 2 1 ] \begin{bmatrix} F_{n}\\\\ F_{n-1}\\\\ n^3\\\\ n^2\\\\ 1\\ \end{bmatrix} = \begin{bmatrix} & & & & \\\\ & & & & \\\\ & & & & \\\\ & & & & \\\\ & & & & & &&& \\ \end{bmatrix} \times \begin{bmatrix} F_{n-1}\\\\ F_{n-2}\\\\ (n-1)^3\\\\ (n-1)^2\\\\ 1\\ \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡FnFn−1n3n21⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤×⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡Fn−1Fn−2(n−1)3(n−1)21⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
其他项的原则是缺什么补什么,当要表示 n 3 n^3 n3以及 n 2 n^2 n2时,我们会发现,需要补一个 n n n才能表示出它们:
[ F n F n − 1 n 3 n 2 n 1 ] = [ ] × [ F n − 1 F n − 2 ( n − 1 ) 3 ( n − 1 ) 2 n 1 ] \begin{bmatrix} F_{n}\\\\ F_{n-1}\\\\ n^3\\\\ n^2\\\\ n\\\\ 1\\ \end{bmatrix} = \begin{bmatrix} & & & & \\\\ & & & & \\\\ & & & & \\\\ & & & & \\\\ & & & & \\\\ & & & & & &&& \\ \end{bmatrix} \times \begin{bmatrix} F_{n-1}\\\\ F_{n-2}\\\\ (n-1)^3\\\\ (n-1)^2\\\\ n\\\\ 1\\ \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡FnFn−1n3n2n1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤×⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡Fn−1Fn−2(n−1)3(n−1)2n1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
需要的项都够了之后,只需仔细的写出系数矩阵即可。
[ F n F n − 1 n 3 n 2 n 1 ] = [ 1 0 1 4 5 − 2 1 0 0 0 0 0 0 0 1 3 3 − 2 0 0 0 1 2 − 1 0 0 0 0 1 0 0 0 0 0 0 1 ] × [ F n − 1 F n − 2 ( n − 1 ) 3 ( n − 1 ) 2 n − 1 1 ] \begin{bmatrix} F_{n}\\\\ F_{n-1}\\\\ n^3\\\\ n^2\\\\ n\\\\ 1\\ \end{bmatrix} = \begin{bmatrix} 1&0&1&4&5&-2\\\\ 1&0&0&0&0&0\\\\ 0&0 &1 &3 &3 &-2\\\\ 0& 0& 0& 1& 2&-1\\\\ 0& 0& 0& 0& 1&0\\\\ 0& 0& 0& 0& 0&1\\ \end{bmatrix} \times \begin{bmatrix} F_{n-1}\\\\ F_{n-2}\\\\ (n-1)^3\\\\ (n-1)^2\\\\ n-1\\\\ 1\\ \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡FnFn−1n3n2n1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡110000000000101000403100503210−20−2−101⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤×⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡Fn−1Fn−2(n−1)3(n−1)2n−11⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
代码
/*
Fn = Fn-1 + n^3 + n^2 + 1
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 8;
const int mod = 13331;
int LEN;
struct Matrix
{
int M[N][N];
};
void init(Matrix &a, int opt) //opt=0 初始化为零矩阵
{
for (int i = 1; i <= LEN; i++)
for (int j = 1; j <= LEN; j++)
a.M[i][j] = (opt == 1 && i == j);
}
Matrix multi(Matrix a, Matrix b)
{
Matrix res;
init(res, 0);
for (int i = 1; i <= LEN; i++)
for (int j = 1; j <= LEN; j++)
for (int k = 1; k <= LEN; k++)
{
res.M[i][j] += a.M[i][k] * b.M[k][j];
res.M[i][j] %= mod;
}
return res;
}
Matrix quick_multi(Matrix a, int t) //矩阵快速幂
{
Matrix res;
init(res, 1); //初始化为单位矩阵
while (t)
{
if (t & 1)
res = multi(res, a);
t /= 2;
a = multi(a, a);
}
return res;
}
int BEG[7][7] = {{},
{0, 1},
{0, 0},
{0, 1},
{0, 1},
{0, 1},
{0, 1}};
int BASE[7][7] = {
{},
{0, 1, 0, 1, 4, 5, 3},
{0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 3, 3, 1},
{0, 0, 0, 0, 1, 2, 1},
{0, 0, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 0, 1}};
void solve()
{
int n;
cin >> n;
LEN = 6;
Matrix ans, beg, base;
for (int i = 1; i <= LEN; i++)
for (int j = 1; j <= LEN; j++)
base.M[i][j] = BASE[i][j], beg.M[i][j] = BEG[i][j];
base = quick_multi(base, n - 1);
init(ans, 0);
for (int i = 1; i <= LEN; i++)
for (int j = 1; j <= 1; j++)
for (int k = 1; k <= LEN; k++)
{
ans.M[i][j] += base.M[i][k] * beg.M[k][j];
ans.M[i][j] %= mod;
}
cout << ans.M[1][1] << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
例题
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
const int mod = 10000;
struct Matrix
{
int M[5][5];
};
Matrix multi(Matrix a, Matrix b)
{
Matrix ans;
for (int i = 1; i <= 2; i++)
for (int j = 1; j <= 2; j++)
ans.M[i][j] = 0;
for (int i = 1; i <= 2; i++)
for (int j = 1; j <= 2; j++)
for (int k = 1; k <= 2; k++)
{
ans.M[i][j] += a.M[i][k] * b.M[k][j];
ans.M[i][j] %= mod;
}
return ans;
}
Matrix quick_multi(Matrix base, int t)
{
Matrix res;
res.M[1][1] = res.M[2][2] = 1;
res.M[1][2] = res.M[2][1] = 0;
while (t)
{
if (t & 1)
res = multi(res, base);
t /= 2;
base = multi(base, base);
}
return res;
}
void solve()
{
int n;
while (cin >> n)
{
if (n <= 2)
{
if (n == -1)
return;
if (n == 0)
cout << 0 << endl;
else
cout << 1 << endl;
continue;
}
Matrix beg;
beg.M[1][1] = 1;
beg.M[1][2] = 1;
Matrix base;
base.M[1][1] = 0;
base.M[1][2] = 1;
base.M[2][1] = 1;
base.M[2][2] = 1;
base = quick_multi(base, n - 2);
Matrix ans;
for (int i = 1; i <= 2; i++)
for (int j = 1; j <= 2; j++)
ans.M[i][j] = 0;
for (int i = 1; i <= 1; i++)
for (int j = 1; j <= 2; j++)
for (int k = 1; k <= 2; k++)
{
ans.M[i][j] += beg.M[i][k] * base.M[k][j];
ans.M[i][j] %= mod;
}
cout << ans.M[1][2] << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
solve();
return 0;
}
/*
*/