线性代数在OI中的应用(hjyowl精讲)

今天,我们来讲解一下线性代数在OI(算法竞赛)中的具体讲解啊这些的,0基础也能学懂。

定义

矩阵的定义就是一个n行m列的矩阵,他的每一行长度为m,每一列长度为n,这是一些实例:

       (1,0,0,1,0)
A(3,5)=(1,0,1,0,0)
       (1,1,0,0,1)

比如,这个就是一个矩阵,这是矩阵的初始定义,一般,后面我们简称为n*m的矩阵。

基础运算

一个n1*m1的矩阵和一个n2*m2的矩阵相加得到的是max(n1,n2)*max(m1,m2)的矩阵,他其实就是把每个矩阵变成一个max(n1,n2)*max(m1,m2)的矩阵(新增加的的地方的值都为0),然后答案矩阵res的第i行j列(以后这种简称res[i][j])=第一个矩阵a[i][j]+第二个矩阵b[i][j]的值,这个非常好理解,减法也是一样的,只不过把加变成减。

最难的是乘法(除法一般不用),定义如下:

乘法需要用一个大小为n*m和m*p的矩阵相乘(没打错,第一个的列数和第二个的行数必须一样),得到一个n*p的矩阵,然后定义如下:

对于答案矩阵c的c[i][j],c[i][j]=c[i][j]+c[i][k]*c[k][j](这里的k是枚举的另外一个,由1~m)

所以说就完了,当然,矩阵有一些性质,下面会给出

性质

1.矩阵满足结合律

2.矩阵满足分配律

3.矩阵不满足交换律(容易看出,因为交换了有可能乘不了,而且答案也不一样)

一些特殊的常用矩阵:

1.方阵:

这种是最重要的,因为方阵可以用快速幂(不会的自己查)求次方,因为他的中间不会出现无法相乘等问题,这个是OI种最常用的,基本上都是这样的。

2.0矩阵:

这个矩阵也很重要,他一般用来做初值,就相当于数字1那种。

矩阵在OI中的应用:

呵呵,前面都是一些铺垫,这个才是重头戏。

矩阵一般是用来加速dp,这也是为什么我们之前要讲解几期dp,一般来讲,加速的dp是线性dp,而且不能有其他参数,只通过一些系数和之前的状态还有一定的常数来转移的。比如说斐波那契额数列就是可以用矩阵快速幂加速的,这是什么原理呢?我们来看一下:

一般来说,根据矩阵的性质,对于任何方阵a,他的n次方是可以用快速幂来计算的(不会的自己查),因为它满足分配律和结合律,只要稍微想一下就可以发现,快速幂不涉及交换律,所以说快速幂是可行的,可以有效降低时间复杂度,可以让复杂度变成O(log n),这个效率极其的高!正常的dp是o(n),对于10^9的n就已经跑不动了,而矩阵快速幂!理论上来说,他是可以轻松的跑2^10000的数据!只不过需要高精度。

题目

好吧,我们现在看一个例题:

斐波那契额数列第0项为0,第1项为1,对于>1的项数,f[i]=f[i-1]+f[i-2],请你求出f[n]的值。n<=10^9.

大家看看能不能写出来dp,dp很简单,但是一定会超时,我们来思考一下,如下:
 

我们需要一个矩阵A,他乘上矩阵{f[i-1]} 等于{f[i]}求出矩阵A.

                                                      {f[i]}       {f[i+1]}

首先,可以发现矩阵一定是一个2*2的(由乘法的定义得知),我们尝试构造一下。

第一行,有两个元素A[1][1]和A[1][2],A[1][1]*f[i-1]+A[1][2]*f[i]=f[i],轻松知道f[1][1]=0,f[1][2]=1

第二行,有两个元素A[2][1]和A[2][2],A[2][1]*f[i-1]+A[2][2]*f[i]=f[i+1],由斐波那契额数列的转移得知分别为1,1(f[i-1]+f[i]=f[i+1]),所以说构造矩阵:

(0,1)
(1,1)

在代码里面定义矩阵A(直接用一个二维数组),然后我们还需要设置一个最后的结果,他的初始值应该是{0,1}(因为斐波那契额数列的定义),所以说我们只需要把这个{0,1}乘上A^n,然后就得到了{f[i],f[i+1]},所以说我们只需要输出最后结果的第一项就可以了。

这就是代码:

#include <bits/stdc++.h>
using namespace std;
const int mod = 10000;
int n;
const int N = 1010;
void mul_qmi1(int f[3],int a[3][3]){//这是一个1*2乘上2*2的函数
	int c[3];
	memset(c,0,sizeof c);
	for (int i = 1; i <= 2; i ++ ){
		for (int j = 1; j <= 2; j ++ ){
			c[i] = (c[i] + (long long)f[j] * a[j][i]) % mod;
		}
	}
	memcpy(f,c,sizeof c);
}
void mul_qmi2(int a[3][3]){//这是一个2*2的矩阵自乘的结果
	int c[3][3];
	memset(c,0,sizeof c);
	for (int i = 1; i <= 2; i ++ ){
		for (int j = 1; j <= 2; j ++ ){
			for (int k = 1; k <= 2; k ++ ){
				c[i][j] = (c[i][j] + (long long)a[i][k] * a[k][j]) % mod;
			}
		}
	}
	memcpy(a,c,sizeof c);
}
void qmi(int k){//这是快速幂
	int f[3];
	int a[3][3];
	f[1] = 0;
	f[2] = 1;
	a[1][1] = 0;
	a[1][2] = 1;
	a[2][1] = 1;
	a[2][2] = 1;
	while (k){
		if (k & 1){
			mul_qmi1(f,a);
		}
		mul_qmi2(a);
		k >>= 1;
	}
	cout << f[1] << endl;
}
int main(){
	int n;
    cin >> n;
    qmi(n);
	return 0;
}

好的,今天就到这里,布置一个练习题,下次我们继续讲解,下次会继续上难度,再见。

例题:P1939 矩阵加速(数列) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  • 31
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值