矩阵乘法与矩阵快速幂

1 矩阵乘法

1.1 定义

对于两个矩阵 A,B,其中 A 大小为 n×mB 大小为 m×p,则这两个矩阵可以做乘法,得到的矩阵 C 的大小为 n×p

例如:

A=[a11a12a13a21a22a23]

B=[b11b12b21b22b31b32]

则有:

C=AB=[a11a12a13a21a22a23][b11b12b21b22b31b32]=[a11b11+a12b21+a13b31a11b12+a12b22+a13b32a21b11+a22b21+a23b31a21b12+a22b22+a23b32]

1.2 注意点

  • 只有当矩阵 A,B 满足 A 的列数等于 B 的行数时,才能进行矩阵乘法。
  • 矩阵乘法不满足交换律(这点很重要)

1.3 代码

提供一个封装模板。

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int Maxn = 205;

int n, m, p;

struct matrix {
	int n, m, p[Maxn][Maxn];
	matrix operator * (const matrix &b) const {
		matrix a = *this, c;
		c.n = a.n, c.m = b.m;
		for(int i = 1; i <= a.n; i++) {
			for(int j = 1; j <= a.m; j++) {
				for(int k = 1; k <= b.m; k++) {
					c.p[i][k] += a.p[i][j] * b.p[j][k];
				}
			}
		}
		return c;
	}
	void print() {
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				cout << p[i][j] << " ";
			}
			cout << '\n';
		}
	}
}A, B, C;

signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> m;
	A.n = n, A.m = m;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			cin >> A.p[i][j];
		}
	}
	cin >> p;
	B.n = m, B.m = p;
	for(int i = 1; i <= m; i++) {
		for(int j = 1; j <= p; j++) {
			cin >> B.p[i][j];
		}
	}
	C = A * B;
	C.print();
	return 0;
}

2 矩阵快速幂

2.1 定义

仿照着数字的快速幂,我们也能快速求出矩阵的幂。

定义矩阵的幂为:An=AAAA

这很明显需要满足原矩阵为正方形。

2.2 用途

矩阵快速幂的经典应用就是加速递推。

直接举几个例子。

2.2.1 「例 1」Fibonacci 第 n 项

Problem

求斐波那契数列第 n 项后四位(n109

Solution

第一眼:这不是递推板题吗!

然而 n109O(n) 暴力绝对爆炸。

我们运用矩阵快速幂加速递推。

接下来我们来求一下递推式(注意掌握方法):

我们先来写出 Fn 的部分:

[Fn  ]=[11  ][Fn1Fn2]

接下来我们关注右边的第二个矩阵,发现他们的下标相差 1,因此左边的矩阵应该是 [FnFn1]

最后补全整个式子:

[FnFn1 ]=[1110][Fn1Fn2]

因此我们对于矩阵 [1110] 不断做快速幂,然后乘上 [F1F0] 即可。

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int Maxn = 205;

int n, mod;

struct matrix {
	int n, m, p[Maxn][Maxn];
	void init() {
		n = 0, m = 0, memset(p, 0, sizeof p);
	}
	matrix operator * (const matrix &b) const {
		matrix a = *this, c;
		c.init();
		c.n = a.n, c.m = b.m;
		for(int i = 1; i <= a.n; i++) {
			for(int j = 1; j <= a.m; j++) {
				for(int k = 1; k <= b.m; k++) {
					c.p[i][k] = (c.p[i][k] + a.p[i][j] * b.p[j][k] % mod) % mod;
				}
			}
		}
		return c;
	}
	matrix operator ^ (const int &o) const {
		matrix res, a = *this; int b = o;
		res.init();
		res.n = res.m = 2;
		res.p[1][1] = res.p[2][2] = 1;
		while(b) {
			if(b & 1) {
				res = res * a;
			}
			a = a * a;
			b >>= 1;
		}
		return res;
	}
	void print() {
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				cout << p[i][j] << " ";
			}
			cout << '\n';
		}
	}
}A;

signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> mod; 
	A.p[1][1] = A.p[1][2] = A.p[2][1] = 1;
	A.n = A.m = 2;
	A = A ^ (n - 1);
	cout << A.p[1][1];
	return 0;
}

2.2.2 「例 2」P207 迷路

Solution

我们发现如果边权为 1 ,那么直接将矩阵 T 次方即可。

但是此时边权不为 1,所以不能这样做。

但是我们又会发现这个边权居然最高才 9,因此我们考虑一下分层图的思路。

将一个点拆成 9 个点并顺次相连,这样能将边权转化为 1。举个例子:

对于这样一条边,我们将 12 两个点拆开,然后将他们按照原先边权连起来,如下图:

这样从 11 走到 21,经过的边权仍然是 6,但我们就转化成了边权为 1 的图。

注意最后统计答案时统计的是点 n1 的值而非 n 的所有值的和。

接下来注意细节即可。

Code

#include <bits/stdc++.h>

/*
我们发现如果边权为 1,那就是一道求 t 次方的大板子题
然而这题有边权,所以不能这么干
我们考虑分层图的思想,既然边权才 9,那我们直接将一个点暴力拆成九个点,然后边权就能转化为 1
这下就是大板子题了,在求 t 次方即可 
*/

using namespace std;

typedef long long LL;
const int Maxn = 205;
const int Mod = 2009;

int n, t, m;

struct matrix {
	int n, m, p[Maxn][Maxn];
	void init() {
		n = 0, m = 0, memset(p, 0, sizeof p);
	}
	matrix operator * (const matrix &b) const {
		matrix a = *this, c;
		c.init();
		c.n = a.n, c.m = b.m;
		for(int i = 1; i <= a.n; i++) {
			for(int j = 1; j <= a.m; j++) {
				for(int k = 1; k <= b.m; k++) {
					c.p[i][k] = (c.p[i][k] + a.p[i][j] * b.p[j][k] % Mod) % Mod;
				}
			}
		}
		return c;
	}
	matrix operator ^ (const int &o) const {
		matrix res, a = *this; int b = o;
		res.init();
		res.n = res.m = a.n;
		for(int i = 1; i <= n; i++) {
			res.p[i][i] = 1;
		}
		while(b) {
			if(b & 1) {
				res = res * a;
			}
			a = a * a;
			b >>= 1;
		}
		return res;
	}
	void print() {
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				cout << p[i][j] << " ";
			}
			cout << '\n';
		}
	}
}A;

signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> t;
	m = n * 9;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= 8; j++) {
			A.p[9 * (i - 1) + j][9 * (i - 1) + j + 1] = 1;
		}
	}
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			char ch;
			cin >> ch;
			if(ch > '0') {
				A.p[9 * (i - 1) + ch - '0'][9 * (j - 1) + 1] = 1;
			}
		}
	}
	A.n = A.m = m;
	A = A ^ t;
	cout << A.p[1][9 * (n - 1) + 1];
	return 0;
}

2.2.3 「例 3」佳佳的 Fibonacci

Problem

(F1+2F2+3F3++nFn)modm 的值。

Solution

我们还是运用矩阵快速幂。

但是我们发现这个形式似曾相识,还记得求区间修改区间查询的 BIT 时候的式子吗?他和这个形式是一样的。

我们还是再来推一遍:

先设 Si=F1+F2++Fi

然后有:

F1+2F2+3F3++nFn=n×SnSn1Sn2S1=n×Sni=1n1Si

然后我们再令 Pn=i=1n1Si,则此时原式就可以化为:

n×SnPn

接下来我们递推 Sn,Pn 来得到原式。

还是分步讨论:

写出关于 Pn 的部分。注意到 Pn=Pn1+Sn1

[Pn   ]=[1100            ][Pn1Sn1  ]

注意到右边第二个矩阵要满足 SP 下标相等,所以左边还要满足。又注意到 Sn=Sn1+Fn,所以再补全一部分:

[PnSn  ]=[11  011         ][Pn1Sn1Fn ]

又注意到右边 F 的下标比 S 的下标多 1,因此左边应该是 Fn+1。又注意到 Fn+1=Fn+Fn1,所以补全为:

[PnSnFn+1Fn]=[1100011000110010][Pn1Sn1FnFn1]

然后实现即可。

Code

十年 OI 一场空,不开 long long 见祖宗。

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int Maxn = 205;

int n, mod;

struct matrix {
	int n, m, p[Maxn][Maxn];
	void init() {
		n = m = 0;
		memset(p, 0, sizeof p);
	}
	void print() {
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				cout << p[i][j] << " ";
			}
			cout << '\n';
		}
	}
	matrix operator * (const matrix &b) const {
		matrix a = *this, c;
		c.init();
		c.n = a.n, c.m = b.m;
		for(int i = 1; i <= a.n; i++) {
			for(int j = 1; j <= a.m; j++) {
				for(int k = 1; k <= b.m; k++) {
					c.p[i][k] = (c.p[i][k] + a.p[i][j] * b.p[j][k] % mod) % mod;
				}
			}
		}
		return c;
	}
	matrix operator ^ (const int &b) const {
		int p = b;
		matrix a = *this, res;
		res.init();
		res.n = res.m = a.n;
		for(int i = 1; i <= a.n; i++) {
			res.p[i][i] = 1;
		}
		while(p) {
			if(p & 1) {
				res = res * a;
			}
			a = a * a;
			p >>= 1;
		} 
		return res;
	}
}A, F;

signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> mod;
	A.init(), F.init();
	A.n = A.m = 4;
	A.p[1][1] = A.p[1][2] = A.p[2][2] = A.p[2][3] = A.p[3][3] = A.p[3][4] = A.p[4][3] = 1;
	F.n = 4, F.m = 1;
	F.p[3][1] = 1;
	A = A ^ n;
	A = A * F;
	cout << (n * A.p[2][1] % mod - A.p[1][1] + mod)	% mod;
	return 0;
}
posted @   UKE_Automation  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示