\(P1939\) 【模板】矩阵加速(数列)
一、题目描述
二、解题思路
- 从左到右,从小到大 【推荐写法】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9 + 7;
const int N = 4;
int n;
LL f[N][N], b[N][N];
void init() {
memset(f, 0, sizeof(f));
f[1][1] = f[1][2] = f[1][3] = 1;
// base数组,原理见题解
memset(b, 0, sizeof(b));
b[1][3] = b[2][1] = b[3][2] = b[3][3] = 1;
}
//矩阵乘法
void mul(LL C[][N], LL A[][N], LL B[][N]) {
LL t[N][N] = {0};
for (int i = 1; i < N; i++)
for (int j = 1; j < N; j++)
for (int k = 1; k < N; k++)
t[i][j] = (t[i][j] + (A[i][k] * B[k][j] % MOD)) % MOD;
memcpy(C, t, sizeof t);
}
//快速幂
void qmi(int k) {
while (k) {
if (k & 1) mul(f, f, b);
mul(b, b, b);
k >>= 1;
}
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
if (n <= 3) {
printf("1\n");
continue;
}
init();
qmi(n - 1);
printf("%lld\n", f[1][1]);
}
}
\(Q:\)为什么是\(n-1\)次幂?
\(A:\)从小到大的初始矩阵,最初时,第一行第一列是\(f(1)\),它乘上一个\(base\)可以到达\(f(2)\),那么,需要到达\(f(n)\),当然是乘上\(n-1\)个\(base\)矩阵了。
- 从左到右,从大到小
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9 + 7;
const int N = 4;
int n;
LL f[N][N], b[N][N];
void init() {
memset(f, 0, sizeof(f));
f[1][1] = f[1][2] = f[1][3] = 1;
memset(b, 0, sizeof(b));
b[1][1] = b[1][2] = b[2][3] = b[3][1] = 1;
}
void mul(LL C[][N], LL A[][N], LL B[][N]) {
LL t[N][N] = {0};
for (int i = 1; i < N; i++)
for (int j = 1; j < N; j++)
for (int k = 1; k < N; k++)
t[i][j] = (t[i][j] + (A[i][k] * B[k][j] % MOD)) % MOD;
memcpy(C, t, sizeof t);
}
void qmi(int k) {
while (k) {
if (k & 1) mul(f, f, b);
mul(b, b, b);
k >>= 1;
}
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
if (n <= 3) {
printf("1\n");
continue;
}
init();
qmi(n - 3);
printf("%lld\n", f[1][1]);
}
}
\(Q:\)为什么是\(n-3\)次幂?
\(A:\)从大到小的初始矩阵,最初时,第一行第一列是\(f(3)\),它乘上一个\(base\)可以到达\(f(4)\),那么,需要到达\(f(n)\),当然是乘上\(n-3\)个\(base\)矩阵了。
三、疑问与解答
\(Q1\):为什么我看到有人写的时候用的是一维的\(f\)数组,你是用的二维?有什么区别?
\(A\):其实真正有用的确定是一维,我这里用的二维是因为我懒:矩阵快速幂的模板有多套,我自己整理了一个我喜欢的样式
void mul(LL C[][N], LL A[][N], LL B[][N]) {
LL t[N][N] = {0};
for (int i = 1; i < N; i++)
for (int j = 1; j < N; j++)
for (int k = 1; k < N; k++)
t[i][j] = (t[i][j] + (A[i][k] * B[k][j] % MOD)) % MOD;
memcpy(C, t, sizeof t);
}
void qmi(int k) {
while (k) {
if (k & 1) mul(f, f, b);
mul(b, b, b);
k >>= 1;
}
}
网上网友提供的模板:
- 通过函数重载写成两个\(mul\)函数的,一个用于计算\(A \times B\),然后结果保存到\(A\)中,另一个用于计算\(A \times A\),结果保存到\(A\)中,还特别支持了一维数组传入,这样背模板就太麻烦了,我不喜欢。
- 通过声明一个结构体,重载乘法运算符,太繁琐,我也不喜欢。
我的这个模板具有如下优点:
- 支持二维相同,不同,矩阵相乘,结果可以保存回原数组,也可以赋值新数组
- 代码简洁
- 一维的矩阵,可以通过构造二维的方法传入,一样可以解决。
四、遗留问题
矩阵快速幂——从入门到放弃(看这一篇就够了)
https://www.codetd.com/article/12024444