[线性代数xOI/ACM]系数矩阵的QGXZ分解

一些无关紧要的Q&A

Q:你是怎么想到这个花里胡哨的算法的啊?
A:前几天学习线性代数时有幸和Magolor大佬讨论到 LU 分解在多解时的时间复杂度问题,于是yy出了这个奇怪(?)的算法。

Q:为什么叫 QGXZ 分解呀?你是不是在装逼啊?
A:这个名字是Magolor大佬起的,我也只能无条件服从咯~ 如有雷同绝非学术不端~

Q:Magolor大佬太强啦~
A:恭喜我们达成了共识~

概述

QGXZ 分解,是用于解决多线性方程组通解问题的算法。具体来讲:

给出 n×m 的系数矩阵 A ,分别求 Ax=b1,Ax=b2,...,Ax=bq通解 ,其中 bin×1 的列向量。以下假设 n,m,q 同阶。

如果对 bi 强制在线的话,朴素算法的时间复杂度为 O(n4) 。如果对矩阵进行 QGXZ 分解,则复杂度降为 O(n3)

前置技能

QGXZ 分解本质上是 LU 分解的扩展,因此先来介绍一下 LU 分解。

LU 分解是对于一个 n×m 的矩阵,将其分解为一个 n×n 的下三角矩阵 L 和一个 n×m 的上梯形矩阵 U 的乘积的结果,即 A=L×U

求法:对于矩阵 A ,从上到下进行矩阵行变换过程(这里仅考虑第三种行变换:将一行乘以一个数加到零一行上)。我们知道,使用一次行变换将 A 变成 B 的过程可以使用 A=K×B 的形式描述,其中 K 是变换矩阵。由于在用上消下的前提下 K 是下三角矩阵,而下三角矩阵的乘积也是下三角矩阵,因此每次的变换矩阵的乘积就是我们所求的下三角矩阵 L ,而 A 的最终结果也是上梯形矩阵 U

例如:

(110010112100)=(100110201)(110001110100)=(100110211)(110001110011)

LU 分解有什么用?

假如现在有方程组 Ax=b ,它就等价于 LUx=b 。我们可以把 Ux 当作一个整体 y ,先解方程 Ly=b ,然后再解 Ux=y 。显然这两个方程都比较 “容易” 解出。

局限性

LU 分解有两点局限性:

  1. 由于行变换的过程必须是使用上边的行消下边的行,因此对于一些矩阵可能不能直接进行 LU 分解;就算能进行 LU 分解,在处理小数时不能实现 “使用当前元系数绝对值最大的行消其余的行” ,精确度也就无法得到保证。

  2. 即使矩阵能够进行 LU 分解,在解方程 Ux=y 时,如果方程有多解,则主元需要使用自由元来表示。而在代入求解的过程中,有 O(n) 个方程,每个方程要代入 O(n) 个主元,每个主元要用 O(n) 个自由元表示,因此就算知道了系数矩阵 LU 分解的形式,一次代入的复杂度也是 O(n3) 的,和暴力没有区别。

下面我们介绍 GXZ 分解和 QGXZ 分解来解决这两点局限性。

GXZ 分解

GXZ 分解是对于一个 n×m 的矩阵,将其分解为一个 n×n 的下三角矩阵 G 、一个 n×n 的上三角矩阵 X 和一个 n×m 的简化行阶梯矩阵(每个主元所在列的其它位置都是 0 的行阶梯矩阵) Z 的乘积的结果,即 A=G×X×Z

这个求法也很简单:在LU分解使用行变换正消得到变换矩阵 L 和行阶梯矩阵 U 后,我们再反消一波,用主元行将上面行的相应位置消成 0 ,并使用同 LU 分解的方法记录变换矩阵。由于每次都是用下面消上面,因此变换矩阵必然是上三角矩阵(和 LU 分解类似)。

在偷换一波变量名后便有 A=GXZ

例如:

(110010112100)=(100110211)(110011001)(100001000011)

这样的话,只需要解方程 Gd=bXe=dZx=e 即可。前两个方程显然是 O(n2) 的,而第三个方程只需要表示主元且没有代入过程,也是 O(n2) 的。

于是我们就得到了一个 O(n3) 预处理, O(n2) 单次询问的算法。

QGXZ 分解

GXZ 分解处理了第二点局限性,第一点局限性则由 QGXZ 分解来解决。

QGXZ 分解即将 n×m 的矩阵分解成置换矩阵 QGXZ 分解的乘积的形式。

具体方法:在 GXZ 分解的第一步(LU分解)时,假设当前已经消成了 A=L0U0 的形式,进一步变换消元时发现需要交换 U0 的某两行,也即 U0=T0U1 ,其中 T0 是置换矩阵。我们现在要做的就是将 L0T0U1 变成 T1L1U1 ,即把 L0T0 变成 T1L1

我们知道,L0T0 相当于交换 L0 的某两列,而 T1L1 相当于交换 L1 的某两行。由于我们消元的过程是从上到下进行的,因此 L0 要交换的两列必然是只有主对角线是 1 ,其余位置为 0

因此,我们只需要手动交换 L0 相应两行的主对角线前面的部分作为 L1 ,然后直接把 T0 拿到前面,原封不动作为 T1 即可。

例如:我们要交换 L02 列和第 3 列,则手动交换 L02 行和第 3 行的前 min(2,3)1 个数作为 L1 ,把 T0 拿到 L0 前面作为 L1 即可。也即:

(100x10y01)(100001010)=(100001010)(100y10x01)

每次交换都进行这样的过程,这样我们就把置换矩阵和置换矩阵放到了一起,把下三角矩阵和下三角矩阵放到了一起。由于它们的乘积都不会改变矩阵的特殊性质,因此最终的 Q 必然也是置换矩阵,G 必然也是下三角矩阵。

到此,解 Ax=b 就变为:分解 A=Q×G×X×Z ,然后分别解 Qc=bGd=cXe=dZx=e 即可。

单次询问的时间复杂度还是 O(n2) 不变。

代码

老年选手不保证代码正确性(

#include <bits/stdc++.h>
#define N 510
#define eps 1e-6
using namespace std;
int pos[N];
double Q[N][N] , G[N][N] , X[N][N] , Z[N][N] , b[N] , c[N] , d[N] , e[N];
int main()
{
	int n , m , q , i , j , k , p = 0 , t;
	double mx;
	scanf("%d%d%d" , &n , &m , &q);
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= m ; j ++ )
			scanf("%lf" , &Z[i][j]);
	for(i = 1 ; i <= n ; i ++ ) Q[i][i] = G[i][i] = X[i][i] = 1;
	for(i = 1 ; i <= m ; i ++ )
	{
		t = 0 , mx = eps;
		for(j = p + 1 ; j <= n ; j ++ )
			if(abs(Z[j][i]) > mx)
				t = j , mx = abs(Z[j][i]);
		if(!t) continue;
		pos[ ++ p] = i;
		for(k = i ; k <= m ; k ++ ) swap(Z[p][k] , Z[t][k]);
		for(k = 1 ; k <= n ; k ++ ) swap(Q[p][k] , Q[t][k]);
		for(k = 1 ; k < p ; k ++ ) swap(G[p][k] , G[t][k]);
		for(j = p + 1 ; j <= n ; j ++ )
		{
			G[j][p] = Z[j][i] / Z[p][i];
			for(k = i ; k <= m ; k ++ )
				Z[j][k] -= Z[p][k] * G[j][p];
		}
	}
	for(i = p ; i ; i -- )
	{
		for(j = i - 1 ; j ; j -- )
		{
			X[j][i] = Z[j][pos[i]] / Z[i][pos[i]];
			for(k = pos[i] ; k <= m ; k ++ )
				Z[j][k] -= Z[i][k] * X[j][i];
		}
	}
	while(q -- )
	{
		for(i = 1 ; i <= n ; i ++ ) scanf("%lf" , &b[i]);
		for(i = 1 ; i <= n ; i ++ )
			for(j = 1 ; j <= n ; j ++ )
				if(Q[i][j] == 1)
					c[j] = b[i];
		for(i = 1 ; i <= n ; i ++ )
		{
			d[i] = c[i];
			for(j = 1 ; j < i ; j ++ )
				d[i] -= G[i][j] * d[j];
		}
		for(i = n ; i ; i -- )
		{
			e[i] = d[i];
			for(j = n ; j > i ; j -- )
				e[i] -= X[i][j] * e[j];
		}
		for(i = p + 1 ; i <= n ; i ++ )
			if(abs(e[i]) > eps)
				break;
		if(i <= n) puts("No solution!");
		else
		{
			for(i = 1 ; i <= p ; i ++ )
			{
				printf("x[%d]=%lf" , pos[i] , e[i] / Z[i][pos[i]]);
				for(j = pos[i] + 1 ; j <= m ; j ++ )
					if(abs(Z[i][j]) > eps)
						printf("%+lfx[%d]" , -Z[i][j] / Z[i][pos[i]] , j);
				puts("");
			}
		}
	}
	return 0;
}
posted @   GXZlegend  阅读(1813)  评论(4编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2017-10-24 【bzoj3997】[TJOI2015]组合数学 Dilworth定理结论题+dp
2017-10-24 【bzoj4009】[HNOI2015]接水果 DFS序+树上倍增+整体二分+树状数组
2017-10-24 【bzoj3488】[ONTAK2010]Highways DFS序+树上倍增+树状数组
2017-10-24 【bzoj2325】[ZJOI2011]道馆之战 树链剖分+线段树区间合并
2017-10-24 【bzoj1260】[CQOI2007]涂色paint 区间dp
2017-10-24 【bzoj3379】[Usaco2004 Open]Turning in Homework 交作业 区间dp
2017-10-24 【bzoj3043】IncDec Sequence 差分
点击右上角即可分享
微信分享提示