矩阵乘法

矩阵乘法入门

矩阵

类似一个二维数组吧。

矩阵的运算

矩阵的加法

Ci,j=Ai,j+Bi,j

我不知道有什么用。

矩阵的减法

Ci,j=Ai,jBi,j

我也不知道有什么用。

矩阵的乘法

Ci,j=kmAi,kBk,j

即答案矩阵的第 i 行第 j 列数是 A 矩阵第 i 行第 k 个数和 B 矩阵第 k 行第 j 列的数相乘再相加的。显然,A 矩阵的列数和 B 矩阵的行数是一样的。注意:矩阵的乘法不满足交换律,但是满足结合律。矩阵的乘法可以用来加速递推。

矩阵乘法代码

struct matrix {
	long long num[3][3];
	void init() {
		memset(num, 0, sizeof(num));
	}
	matrix operator * (const matrix& x) const {
		matrix res;
		res.init();
		for (int i = 1; i <= 2; i ++) 
			for (int j = 1; j <= 2; j ++) 
				for (int k = 1; k <= 2; k ++) 
					res.num[i][j] += num[i][k] * x.num[k][j]
		return res;
	}
};

题目

1. 斐波那契数列

大家都知道,斐波那契数列是满足如下性质的一个数列:

Fn={1 (n2)Fn1+Fn2 (n3)

请你求出 Fnmod109+7 的值。

【数据范围】
对于 60% 的数据,1n92
对于 100% 的数据,1n<263

思路

斐波那契数列的递推式很简单,为 fn=fn1+fn2

对于 60% 的数据,一个一个算就可以了。

对于 100% 的数据,一个一个算直接爆炸了,我们就需要矩阵加速。

求出 fn 需要 fn1fn2 ,所以我们构造一个矩阵 :

[fn1fn2]

我们还需要构造一个矩阵 B 使 [fn1fn2]×B=[fnfn1]

可以看出来,B 一定是一个 2×2 的矩阵。为了更直观的表现,我们在矩阵旁边标上对应的数。

           [fnfn1]

[fn1fn2][1       11       0]

这样就很好理解了,fn,是由 fn1fn2 相加而得的,所以 fn 对应的列, fn1fn2 对应的行上的数都是 1fn1 就是 fn1,所以 fn1 对应的列和 fn1 对应的行上的数是 1,由于 fn1 中不含 fn2 ,所以 fn1 对应的列和 fn2 对应的列上的数是 0

求出了 B 矩阵后我们只要把矩阵 [f2f1] 乘上 n2B 矩阵,就能得到 [fnfn1]

为什么是 n2 次不是 n 次呢?手算一下发现:

[f2f1]×[1110]=[f3f2]

[f3f2]×[1110]=[f4f3]

1 次得到 f3 ,乘 2 次得到 f4,乘 n2 次就得到了 fn

所以:

[f2f1]([11])×[1110]×[1110]×[1110]×...×[1110]=[fnfn1]

[f2f1]×[1110]n2=[fnfn1]

因为 1n<263,直接乘肯定要爆炸,需要快速幂。

矩阵快速幂和数的快速幂非常相似,唯一不同的地方就是 res 需要初始化为单位矩阵,就是对角线上的数都是 1 的矩阵。A×=A, 矩阵中的单位矩阵就类似于数中的 1

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1000000007;
struct matrix {
    LL a[4][4];
    matrix() {memset(a, 0, sizeof(a));}
    matrix operator * (const matrix& x) const {
        matrix res;
        for (int i = 1; i <= 3; i ++) 
            for (int j = 1; j <= 3; j ++) 
                for (int k = 1; k <= 3; k ++) {
                    res.a[i][j] = (res.a[i][j] + (a[i][k] % mod * x.a[k][j] % mod) % mod) % mod;
                }
        return res;
    } 
    matrix operator ^ (LL p) const { 
        matrix res, x;
        for (int i = 1; i <= 3; i ++) res.a[i][i] = 1;
        for (int i = 1; i <= 3; i ++)
            for (int j = 1; j <= 3; j ++)
                    x.a[i][j] = a[i][j];
        while (p) {
            if (p & 1) res = res * x;
            x = x * x;
            p >>= 1;
        }
        return res;
    }
};
matrix m, p;
int main() {
    LL n;
    scanf("%lld", &n);
    if (n <= 2) {
        puts("1");
        return 0;
    }
    p.a[1][1] = p.a[1][2] = p.a[2][1] = 1;
    m.a[1][1] = m.a[1][2] = 1;
    m = m * (p ^ (n - 2));
    printf("%lld\n", m.a[1][1]);
    return 0;
}

2. Fibonacci 前 n 项和

大家都知道 Fibonacci 数列吧。

现在问题很简单,输入 nm,求 fn 的前 n 项和 Sn mod m

思路

这道题就比上道题升级了,需要求前 n 项和。

还是构造一个矩阵:

[Sn2fn1fn2]

与上一题同理,我们需要构造出一个矩阵 B ,使 [Sn2fn1fn2]×B=[Sn1fnfn1]

为了更直观的表现,我们在矩阵旁边标上对应的数太麻烦了,不标了。可以很简单的构造出:

B=[100111010]

Sn1 需要 Sn2+fn1, 所以 Sn1 列上的 Sn2 行和 fn1 行上的数都是 1

剩下的 fn1fn2 都与上一题一样,就略去了。

遇上一题一样,还是快速幂,不过这一次是 n1 次,因为我们初始矩阵中放的是 Sn2

初始矩阵:

[111]

代码

#include <bits/stdc++.h>
using namespace std;
long long n, m;
struct matrix {
	long long num[4][4];
	void init() {
		memset(num, 0, sizeof(num));
	}
	matrix operator * (const matrix& x) const {
		matrix res;
		res.init();
		for (int i = 1; i <= 3; i ++) 
			for (int j = 1; j <= 3; j ++)
				for (int k = 1; k <= 3; k ++)
					res.num[i][j] = (res.num[i][j] + (num[i][k] % m * x.num[k][j] % m) % m) % m;
		return res;
	}
	matrix operator ^ (long long p) const {
		matrix res, a;
		res.init();
		for (int i = 1; i <= 3; i ++)
			res.num[i][i] = 1;
		for (int i = 1; i <= 3; i ++)
			for (int j = 1; j <= 3; j ++)
				a.num[i][j] = num[i][j];
		while (p) {
			if (p & 1) res = res * a;
			a = a * a;
			p >>= 1;
		}
		return res;
	}
};
matrix a, b;
int main() {
	scanf("%lld%lld", &n, &m);
	a.num[1][1] = a.num[1][2] = a.num[1][3] = 1;
	b.num[1][1] = b.num[2][1] = b.num[2][2] = b.num[2][3] = b.num[3][2] = 1;
	a = a * (b ^ (n - 1));
	printf("%lld\n", a.num[1][1]);
	return 0;
}

3. fib

f1=f2=0,fn=7fn1+6fn2+5n+4×3nfn mod 10000

思路

这道题就更加升级了,增加了一些系数和常数,我们把这些全部放入矩阵。

[fn1fn25(n1)53n1]

这样我们的矩阵 B 就变成了一个巨型矩阵:

[71000600001010010110120003]

fn=7fn1+6fn2+5n+4×3n ,所以 fnfn1 行就是 7, fn2 行就是 65n=5(n1)+5,所以 5(n1) 行和 5 行都是 14×3n=12×3n1,所以 3n1 行就是 12

fn1=fn1 与前几题同理,略。

5n=5(n1)+5,所以 5n5(n1) 行和 5 行都是 1

5=5,略。

3n=3n1×3,所以 3n 列,3n1 行就是 3

剩下的就是常规的快速幂,n2 次方。

代码

#include <bits/stdc++.h>
using namespace std;
long long m = 10000;
struct matrix {
	long long num[6][6];
	void init() {
		memset(num, 0, sizeof(num));
	}
	matrix operator * (const matrix& x) const {
		matrix res;
		res.init();
		for (int i = 1; i <= 5; i ++) 
			for (int j = 1; j <= 5; j ++)
				for (int k = 1; k <= 5; k ++)
					res.num[i][j] = (res.num[i][j] + (num[i][k] % m * x.num[k][j] % m) % m) % m;
		return res;
	}
	matrix operator ^ (long long p) const {
		matrix res, a;
		res.init();
		for (int i = 1; i <= 5; i ++)
			res.num[i][i] = 1;
		for (int i = 1; i <= 5; i ++)
			for (int j = 1; j <= 5; j ++)
				a.num[i][j] = num[i][j];
		while (p) {
			if (p & 1) res = res * a;
			a = a * a;
			p >>= 1;
		}
		return res;
	}
};
matrix a, b;
long long n;
int main() {
	while (scanf("%lld", &n) != EOF) {
		if (n == -1) break;
		if (n <= 2) {
			puts("0");
			continue;
		}
		a.num[1][1] = 0,
		a.num[1][2] = 0,
		a.num[1][3] = 10,
		a.num[1][4] = 5,
		a.num[1][5] = 9,
		b.num[1][1] = 7,
		b.num[2][1] = 6,
		b.num[3][1] = 1,
		b.num[4][1] = 1,
		b.num[5][1] = 12,
		b.num[1][2] = 1,
		b.num[3][3] = 1;
		b.num[4][3] = 1,
		b.num[4][4] = 1,
		b.num[5][5] = 3;
		a = a * (b ^ (n - 2));
		printf("%lld\n", a.num[1][1]);
	}
	return 0;
}

总结

构造矩阵时要把递推式中所有项都塞到矩阵里。

矩阵的 Latex 太难了,手都废了。

转眼5个月没写博客了,今天开始继续写。

posted @   maniubi  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示