矩阵ksm
思考问题:
一个基础的斐波那契数列公式:
如果求 的项,该如何求?
# 📕前置芝士
# 🎈ksm
基于一种分块模式的双向累乘
将时间复杂度优化为
默认搜到此篇均会ksm,所以不细讲了,如果不会请去提前了解
inline ll ksm(ll a, ll b, ll mod){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;//底数平方
b >>= 1;//指数减半
}return res;
}
2
3
4
5
6
7
8
# 🎈矩阵乘法
一个的矩阵左乘一个的矩阵会得到一个的矩阵
结果是一个行列的矩阵,其中第行第列位置上的数为:第一个矩阵第行上的个数与第二个矩阵第列上的个数对应相乘后所得的个乘积之和
A[1][1] | A[1][2] | * | B[1][1] | B[1][2] | = | A[1][1]* B[1][1]+A[1][2]* B[2][1] | A[1][1]* B[1][2]+A[1][2]* B[2][2] |
---|---|---|---|---|---|---|---|
A[2][1] | A[2][2] | B[2][1] | B[2][2] | A[2][1]* B[1][1]+A[2][2]* B[2][1] | A[1][2]* B[1][2]+A[2][2]* B[2][2] |
特点
满足结合律和分配律,不满足交换律
只有当矩阵的列数与矩阵的行数相等时,矩阵才有意义
# 📕概述
# 🎈概念&用处
矩阵是一种将递推式转化为矩阵乘法形式,然后与一样优化时间的算法。
# 🎈过程
因为递推式一般都有个初始值
所以形似给你一个数,算它乘上次后的值,这样就是求
所以矩阵的流程便是:
构造初始矩阵
构造连乘矩阵
建立矩阵乘法运算
建立ksm运算
利用第一行求答案
虽然看着很麻烦,但是可以对流程分一下块:
就是对初始值的一种判定构造
就是对我们的递推式进行转换
就是为我们的矩阵ksm建立基础,可以在矩阵结构体内完成
就是求答案
# 📕建立思想
# 😄Fibonacci
构造出的B矩阵
C[n] | = | B | * | C[n-1] | =...= B^{n - 1} * | C[1] | |
---|---|---|---|---|---|---|---|
f[n] | 1 | 1 | f[n - 1] | f[1] | |||
f[n-1] | 1 | 0 | f[n - 2] | f[0] |
这里
矩阵也称“竖矩阵”,用处就是保存所求递推结果。
矩阵也称:递推矩阵”,用处就是与结合加速递推。
易得,且也可以由B同行矩阵从上一次的中获取
从而要求可以利用快速幂运算先求出,最后再做一次矩阵乘法即可(矩阵的第一个元素即为要求的)
# 😄含系数递推式
与上个类似,但是会更普遍一些
B矩阵为
a | b |
1 | 0 |
# 😄跳系数递推式
构造的B矩阵
可视为连续,只不过中间跳过的系数为而已
3 | 0 | 5 | 9 |
1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 |
# 😄带常量递推式
构造的矩阵运算
f[n] | = | a | 0 | b | 1 | * | f[n-1] | = | a | 0 | b | 1 | ^(n-2) | * | f[2] |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
f[n-1] | 1 | 0 | 0 | 0 | f[n-2] | 1 | 0 | 0 | 0 | f[1] | |||||
f[n-2] | 0 | 1 | 0 | 0 | f[n-3] | 0 | 1 | 0 | 0 | f[0] | |||||
c | 0 | 0 | 0 | 1 | c | 0 | 0 | 0 | 1 | c |
# 😄带变量递推式
构造的矩阵运算
写函数来表示变量,此情况可以利用二项式定理:
f[n] | = | 1 | 2 | 1 | 3 | 3 | 1 | ^(n-2) | f[2] |
---|---|---|---|---|---|---|---|---|---|
f[n-1] | 1 | 0 | 0 | 0 | 0 | 0 | f[1] | ||
n^3 | 0 | 0 | 1 | 3 | 3 | 1 | 8 | ||
n^2 | 0 | 0 | 0 | 1 | 2 | 1 | 4 | ||
n | 0 | 0 | 0 | 0 | 1 | 1 | 2 | ||
1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
上例中,可以二项式完再合并同类项
# 😄求数列区间和
, 给定a,b求 ,可转化为求前缀和
S[n] | = | 1 | 1 | 1 | 1 | S[n-1] | = | 1 | 1 | 1 | 1 | ^(n-2) | S[2] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
f[n] | 0 | 1 | 1 | 1 | f[n-1] | 0 | 1 | 1 | 1 | f[2] | ||||
f[n-1] | 0 | 1 | 0 | 0 | f[n-2] | 0 | 1 | 0 | 0 | f[1] | f[n-2] | 0 | 0 | 1 | 0 | f[n-3] | 0 | 0 | 1 | 0 | f[0] |
# 😄平方前缀和并含乘法项的表达式
,求
先平方展开:
S[n] | = | 1 | x^2 | 2xy | y^2 | S[n-1] | = | 1 | x^2 | 2xy | y^2 | ^(n-1) | S[1] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
f[n]^2 | 0 | x^2 | 2xy | y^2 | f[n-1]^2 | 0 | x^2 | 2xy | y^2 | f[1]^2 | ||||
f[n]* f[n-1] | 0 | x | y | 0 | f[n]* f[n-2] | 0 | x | y | 0 | f[1]* f[0] | f[n-1]^2 | 0 | 1 | 0 | 0 | f[n-2]^2 | 0 | 1 | 0 | 0 | f[0]^2 |
# 😄应用
一个循环串,长度为,这个擦混每秒都会进行一次变换,变换规则是:如果左边是,则改变自己的状态,否则保持不变。给定初始状态,问秒以后这个串状态
状态转移方程:定义表示n秒以后,第个字符是还是。
根据同余性质化简得:
然后根据这个递推式构造矩阵即可
# 📕程序实现
# 🎈主功能建立
//其实主功能就是一个Matrix的结构体
const int maxn = /*...*/;
const int mod = /*...*/;
struct Mat{
ll m[maxn][maxn];//j矩阵
Mat(int flag = 0){//初始化构造函数
for(int i = 0; i < maxn; i ++)
for(int j = 0; j < maxn; j ++){
m[i][j] = flag * (i == j);
}
}
inline Mat Mul(Mat a, Mat b){//矩阵乘法
Mat res(0);
for(int i = 0; i < maxn; i ++){
for(int j = 0; j < maxn; j ++){
for(int k = 0; k < maxn; k ++){
res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j] % mod) % mod;
}
}
}
return res;
}
inline Mat ksm(Mat a, ll b){//矩阵的ksm实现
Mat res(1);
while(b){
if(b & 1) res = Mul(res, a);
a = Mul(a, a);
b >>= 1;
}return res;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 🎈题目演示
luogu《矩阵加速(数列)模板》传送门 (opens new window)
思路:
一个跳系数的递推式
建立矩阵结构体
建立B矩阵
建立初始矩阵
运算得B矩阵的幂
利用第一行得出答案
代码:
const int maxn = 3;
const int mod = 1e9 + 7;
struct Mat{
ll m[maxn][maxn];
Mat(int flag = 0){
for(int i = 0; i < maxn; i ++)
for(int j = 0; j < maxn; j ++){
m[i][j] = flag * (i == j);
}
}
inline Mat Mul(Mat a, Mat b){
Mat res(0);
for(int i = 0; i < maxn; i ++){
for(int j = 0; j < maxn; j ++){
for(int k = 0; k < maxn; k ++){
res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j] % mod) % mod;
}
}
}
return res;
}
inline Mat ksm(Mat a, ll b){
Mat res(1);
while(b){
if(b & 1) res = Mul(res, a);
a = Mul(a, a);
b >>= 1;
}return res;
}
};
/*
a[i] 1 0 1 a[i - 1]
a[i - 1] 1 0 0 a[i - 2]
a[i - 2] 0 1 0 a[i - 3]
*/
inline void solve(){
int n; cin >> n;
if(n == 1 || n == 2 || n == 3){
cout << 1 << endl;
return;
}
Mat cur(0);//B矩阵
cur.m[0][0] = 1, cur.m[0][1] = 0, cur.m[0][2] = 1;
cur.m[1][0] = 1, cur.m[1][1] = 0, cur.m[1][2] = 0;
cur.m[2][0] = 0, cur.m[2][1] = 1, cur.m[2][2] = 0;
cur = cur.ksm(cur, n - 3);//ksm运行
cout << (cur.m[0][0] * 1 + cur.m[0][1] * 1 + cur.m[0][2] * 1) % mod << endl;//第一行与初始的竖矩阵运行的结果
}
CHIVAS{
int cass;
EACH_CASE(cass){
solve();
}
_REGAL;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61