Loading

矩阵学习笔记

矩阵学习笔记

持续更新
3Brown1Blue线性代数系列值得你拥有

高斯消元

高斯消元算法的思想是,对于每一个未知量 \(x_i\) ,找到一个 \(x_i\) 的系数非零,但 \(x_1-x_{i-1}\) 的系数为0的方程,然后用初等行变换把其他方程的 \(x_i\) 的系数全部消去

例题

  1. 球形空间产生器
    \(n+1\) 个方程彼此相减得到 \(n\) 个线性方程,然后高斯消元板子即可
#include<bits/stdc++.h>
using namespace std;
double eps=1e-10;
double a[20][20],b[20][20],c[20];
int n;
//a是原数组,b为系数矩阵,c为常数
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n+1;i++)
		for(int j=1;j<=n;j++)scanf("%lf",&a[i][j]);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			b[i][j]=2.0*(a[i][j]-a[i+1][j]);
			c[i]+=(a[i][j]*a[i][j]-a[i+1][j]*a[i+1][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			if(b[j][i]>eps){
				for(int k=1;k<=n;k++)swap(b[i][k],b[j][k]);
				swap(c[i],c[j]);break;
			}
		}
		for(int j=1;j<=n;j++){
			if(i==j)continue;
			double bei=b[j][i]/b[i][i];
			for(int k=1;k<=n;k++)b[j][k]-=b[i][k]*bei;
			c[j]-=c[i]*bei;
		}
	}
	for(int i=1;i<=n;i++){
		printf("%.3lf ",c[i]/b[i][i]);
	}
	printf("\n");
	return 0;
}

矩阵加速dp

这个还是直接看例题吧

例题

1. hh去散步

题意:在一张无向图中,有重边,要求从起点出发,且同一条边不能连续走两次,问经过T次边到达终点的路径一共有几条。 \(n<=50,m<=60,T<=2^{30}\)

k短路!!! 看看标题,让我们用dp来想一想。

假设没有 “有重边、同一条边不能连续走两次” 的条件,设 \(f_{i,j}\) 表示已走 \(i\) 条边,现在在 \(j\) 节点的方案数,\(to(j)\) 表示能够经过一条边到达 \(j\) 的点的集合,则有式子 \(f_{i+1,j}=\sum_{k\in to(j)}f_{i,k}\)

那如果有呢?因为有重边的限制,所以我们可以采用一种很妙的思想,叫做 点边转换 ,将上述过程中的节点变为边。

即设 \(f_{i,j}\) 表示已走 \(i\) 条边,现在走完了第 \(j\) 号边的方案数,\(to(j)\) 表示能够直接到达 \(j\) 号边的集合,则有式子 \(f_{i+1,j}=\sum_{k\in to(j)}f_{i,k}\)

只是小小的改变,我们便解决了重边的问题。可是一看数据范围 \(T<=2^{30}\) !!!怕得要换银河才能过

再看看 \(n,m\) 的数据范围,这就是经典的矩阵加速

不过我们也得想想为什么可以用矩阵。 对于所有 i 确定的 \(f_{i,j}\),我们可以用一个 2*m 的向量来表示当前状态。而转移方程呢?想想是不是可以用一个矩阵来表示其转移关系呢,然后直接相乘呢?不过在这里我们得要弄清楚矩阵每一位的意义,我们才能写对这一个转移矩阵。

设这个 \(2m*2m\) 的转移矩阵为 \(A\),考虑 \(f*A\),结果为向量 \(f'\) ,则 \(\forall i\in[1,n],f'_i=\sum_{j=1}^{2m}f_j*A_{j,i}\) 。那么\(A_{j,i}\) 的意义就为已经走到第 j 号边,现在要走到第 i 号边的方案数。再看条件“同一条边不能连续走两次”,我们得要特判一下反向边

这样我们就可以写出向量 \(f\) 的转移矩阵。 \(f_T=f_1*A^{T-1}\)。又因为矩阵具有结合律,所以我们可以用矩阵快速幂算出 \(A^T\),再与 \(f\) 相乘,最后再统计答案。时间复杂度为 \(O(m^3logT)\)

注意:

  • 使用memcpy()一定要确保操作的两个数组空间大小相同的情况下使用,否则会有想不到的错误(修改越界)发生

  • 一定要理解矩阵代表的具体含义;矩阵乘法不满足交换律

  • 异或(^)的优先级很低,注意要打括号

  • 因为定义,我们需要预处理出初始状态

  • 感觉写结构体封装矩阵或许更好

代码:

/*
 * @Author: fpjo 
 * @Date: 2020-08-24 19:23:41 
 * @Last Modified by: fpjo
 * @Last Modified time: 2020-08-24 21:09:15
 */
#include<bits/stdc++.h>
using namespace std;
#define int long long
int const MOD=45989;
int n,m,t,a,b,tot=1,ans=0;
int h[200],mat[200][200],ori[200];
struct edge{
	int next,to,be;
}e[1000];
void add(int u,int v){
	e[++tot].to=v,e[tot].next=h[u],h[u]=tot;
}
void mul(){
	int x[200];
	memset(x,0,sizeof(x));
	for(int j=2;j<=tot;j++){
		for(int k=2;k<=tot;k++){
			x[j]=(x[j]+(ori[k]*mat[k][j])%MOD)%MOD;
		}
	}
	for(int i=2;i<=tot;i++){
		ori[i]=x[i];
	}
}
void mulself(){
	int x[200][200];memset(x,0,sizeof(x));
	for(int i=2;i<=tot;i++){
		for(int j=2;j<=tot;j++){
			for(int k=2;k<=tot;k++){
				x[i][j]=(x[i][j]+(mat[i][k]*mat[k][j])%MOD)%MOD;
			}
		}
	}
	for(int i=2;i<=tot;i++){
		for(int j=2;j<=tot;j++){
			mat[i][j]=x[i][j];
		}
	}
}
signed main(){
	scanf("%lld%lld%lld%lld%lld",&n,&m,&t,&a,&b);
	for(int i=1;i<=m;i++){
		int u,v;scanf("%lld%lld",&u,&v);
		add(u,v);add(v,u);
	}
	for(int i=2;i<=tot;i++){
		int to=e[i].to;
		for(int j=h[to];j;j=e[j].next){
			if(j!=(i^1)){
				mat[i][j]=1;
			}
		}
	}
	for(int i=h[a];i;i=e[i].next)ori[i]=1;
	t-=1;
	for(;t;t>>=1){
		if(t&1)mul();
		mulself();
	}
	for(int i=h[b];i;i=e[i].next){
		ans=(ans+ori[i^1])%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}

矩阵与数据结构相结合

这个就直接看例题了吧

例题

1. 大魔法师

维护区间上的三亚组 \(A,B,C\) ,满足以下区间操作:

  1. \(A_i=A_i+B_i\)
  2. \(B_i=B_i+C_i\)
  3. \(C_i=C_i+A_i\)
  4. \(A_i=A_i+v(v为常数)\)
  5. \(B_i=B_i*v(同上)\)
  6. \(C_i=v(同上)\)
  7. 查询 \([l,r]\)

码量对于现在的我来说有点大,100+行.

实际上思路很简单,用线段树维护区间,每个节点用一个向量 \([A,B,C,len]\),然后用矩阵分别表示前6个操作即可

就是细节有亿点点多,写了我一个晚上加一个上午

注意取模可以优化,但我没写,所以贼慢

下面贴代码

/*
 * @Author: fpjo 
 * @Date: 2020-08-27 08:13:18 
 * @Last Modified by: fpjo
 * @Last Modified time: 2020-08-27 09:55:43
 */
#include<bits/stdc++.h>
using namespace std;
int const N=25*1e4+10,MOD=998244353;
int n,m,ansa,ansb,ansc;
int a[N][3];
// tag实际上是一个矩阵!而不是opt
struct Mat{
	int mat[4][4];
	int l;
	Mat() {memset(mat,0,sizeof(mat));l=4;}
	const Mat operator *(const Mat &a){
		Mat x;
		for(int i=0;i<l;i++)
			for(int j=0;j<l;j++)
				for(int k=0;k<l;k++)
					if(mat[i][k] && a.mat[k][j])
						x.mat[i][j]=(x.mat[i][j]+(long long)mat[i][k]*a.mat[k][j]%MOD)%MOD;
		return x;
	}
	void operator *= (const Mat &a){
		*this=(*this) * a;
	}
	void operator =(const Mat &a){
		for(int i=0;i<l;i++)
			for(int j=0;j<l;j++)
				mat[i][j]=a.mat[i][j];
	}
    //调试
	void print(){
		for(int i=0;i<l;i++){
			for(int j=0;j<l;j++){
				printf("%d ",mat[i][j]);
			}
			printf("\n");
		}
		printf("--------------------\n");
	}
}trans,ori;
struct seg{
	int l,r;
	Mat m,tag;
}seg[N*10];
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
void build(int x,int l,int r){
	seg[x].tag=ori;
	seg[x].l=l,seg[x].r=r;
	if(l==r){
		seg[x].m.mat[0][0]=a[l][0],seg[x].m.mat[0][1]=a[l][1],seg[x].m.mat[0][2]=a[l][2],seg[x].m.mat[0][3]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	for(int i=0;i<4;i++){
		seg[x].m.mat[0][i]=(seg[x<<1].m.mat[0][i]+seg[x<<1|1].m.mat[0][i])%MOD;
	}
	return;
}
void change(int t,int v){
	if(t==1)trans.mat[1][0]=1;
	else if(t==2)trans.mat[2][1]=1;
	else if(t==3)trans.mat[0][2]=1;
	else if(t==4)trans.mat[3][0]=v;
	else if(t==5)trans.mat[1][1]=v;
	else if(t==6)trans.mat[3][2]=v,trans.mat[2][2]=0;
}
void init(){
	trans=ori;
}
void pushdown(int x){
	seg[x<<1].tag*=seg[x].tag,seg[x<<1|1].tag*=seg[x].tag;
	seg[x<<1].m*=seg[x].tag,seg[x<<1|1].m*=seg[x].tag;
	seg[x].tag=ori;
}
void solve(int x,int l,int r){
	if(l<=seg[x].l&&seg[x].r<=r){
		seg[x].tag*=trans;
		seg[x].m*=trans;
		return;
	}
	pushdown(x);
	int mid=(seg[x].l+seg[x].r)>>1;
	if(l<=mid)solve(x<<1,l,r);
	if(r>mid)solve(x<<1|1,l,r);
	for(int i=0;i<4;i++)seg[x].m.mat[0][i]=((long long)seg[x<<1].m.mat[0][i]+seg[x<<1|1].m.mat[0][i])%MOD;
}
void query(int x,int l,int r){
	if(l<=seg[x].l && seg[x].r<=r){
		ansa=(ansa+seg[x].m.mat[0][0])%MOD,ansb=(ansb+seg[x].m.mat[0][1])%MOD,ansc=(ansc+seg[x].m.mat[0][2])%MOD;
		return;
	}
	pushdown(x);
	int mid=(seg[x].l+seg[x].r)>>1;
	if(l<=mid)query(x<<1,l,r);
	if(r>mid)query(x<<1|1,l,r);
	return;
}
int main(){
	ori.mat[0][0]=1,ori.mat[1][1]=1,ori.mat[2][2]=1,ori.mat[3][3]=1;
	init();
	n=read();
	for(int i=1;i<=n;i++)a[i][0]=read(),a[i][1]=read(),a[i][2]=read();
	build(1,1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int opt,l,r,v=-1;opt=read(),l=read(),r=read();
		if(opt>=4 && opt<=6)v=read();
		if(opt<7){
			change(opt,v);
			solve(1,l,r);
			init();
		}else{
			ansa=0,ansb=0,ansc=0;
			query(1,l,r);
			printf("%d %d %d\n",ansa,ansb,ansc);
		}
	}
	return 0;
}

尾声

学长说矩阵运用的话基本就这三个方面,所以算完结撒花啦!

当然练习题到时候会补的

posted @ 2020-08-25 16:47  fpjo  阅读(236)  评论(3编辑  收藏  举报