[算法] 高斯消元及其应用

主要问题

对于一个线性的方程组求解。

假设这个方程有 n 个,则时间复杂度为 O(n3)

有些题目的 dp 状态有后效性,但是对于线性的方程,可以用高斯消元进行计算。

解决方法

高斯消元法的思路是:通过消元运算,直到得到一个只关于 x1 的式子,只关于 x1,x2 的式子,只关于 x1,x2,x3 的式子,然后回代计算即可。

如下:最初有一个 n 元线性方程

{a1,1x1+a1,2x2+...+a1,nxn=b1a2,1x1+a2,2x2+...+a2,nxn=b2an,1x1+an,2x2+...+an,nxn=bn

将上述方程转换为

{a1,1x1+a1,2x2+...+a1,nxn=b1a1,1x1+a2,2a1,1a2,1x2+...+a2,na1,1a2,1xn=b2a1,1x1+an,2a1,1an,1x2+...+an,na1,1an,1xn=bn

最后通过减法消去后 n1 个方程的 x1

{a1,1x1+a1,2x2+...+a1,nxn=b1(a2,2a1,1a2,1a1,1)x2+...+(a2,na1,1a2,1a1,n)xn=b2b1(an,2a1,1an,1a1,1)x2+...+(an,na1,1an,1a1,n)xn=bnb1

如此,对于剩下的 n1 个方程又是一个新的线性方程求解问题,继续递归求解,最后回代。

若对于当前的子问题中,有一次计算中的 xn 就已经消失了,那么原问题中的这一未知数没有唯一解。

若当前中存在一个方程的各项系数都为 0 ,且该条方程的常数项不为 0 ,则一定无解。

当然,代码具体实现的时候并不需要写递归,直接用循环模拟即可。

实现中还有一个问题,每次求解的 a1,1 是否会对答案产生影响?

可以发现,若 a1,1 太小,那么会导致精度问题,所以每次需要选取主元素:最大的 ai,1 所有方程以它为基础来进行消元。

  • Code:
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define eps 1e-7
void Read(int &n) {
	n = 0; bool f = 0; char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') f = 1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		n = (n << 1) + (n << 3) + (c ^ 48);
		c = getchar();
	}
	if (f) n = -n;
}
const int MAXN = 1e2 + 5;
double a[MAXN][MAXN], ans[MAXN];
int n;
int main() {
	Read(n);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n + 1; j++) scanf("%lf", &a[i][j]);
	for (int i = 1; i <= n; i++) {
		int r = i;
		for (int j = i + 1; j <= n; j++)
			if (fabs(a[r][i]) < fabs(a[j][i])) r = j;//选取主元素
		if (fabs(a[r][i]) < eps) return printf("No Solution"), 0;//不存在唯一解
		if (i != r) swap(a[i], a[r]);
		double tmp = a[i][i];//带入消元
		for (int j = i; j <= n + 1; j++) a[i][j] /= tmp;
		for (int j = i + 1; j <= n; j++) {
			double tmp = a[j][i];
			for (int k = i; k <= n + 1; k++) a[j][k] -= a[i][k] * tmp;
		}
	}
	ans[n] = a[n][n + 1];//回代求解
	for (int i = n - 1; i >= 1; i--) {
		ans[i] = a[i][n + 1];
		for (int j = i + 1; j <= n; j++) ans[i] -= a[i][j] * ans[j];
	}
	for (int i = 1; i <= n; i++) printf("%.2lf\n", ans[i]);
	return 0;
}

模板题目链接。

[JSOI2008]球形空间产生器

题目大意

n 维空间中,给出 n+1 个点,求出圆心的坐标。

规定:

  • 球心:到球面上任意一点距离都相等的点。
  • 距离:设两个 n 维空间上的点 A,B 的坐标为 (a1,a2,,an),(b1,b2,,bn),则AB的距离定义为:dist=(a1b1)2+(a2b2)2++(anbn)2

思路

设坐标数组为 a,由题意得:

j=0n(ai,jxj)2=C

化简得:

j=1n(ai,j2ai+1,j22xj(ai,jai+1,j))=0

将常数项与未知数分离:

j=1n2(ai,jai+1,j)xj=j=1n(ai,j2ai+1,j2)

等价转化为了 n 个线性方程,求解即可。

Code

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define eps 1e-7
const int MAXN = 1e2 + 5;
double a[MAXN][MAXN], b[MAXN][MAXN], ans[MAXN];
int n;
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n + 1; i++)
		for (int j = 1; j <= n; j++) scanf("%lf", &b[i][j]);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			a[i][j] = 2 * (b[i][j] - b[i + 1][j]);
			a[i][n + 1] += b[i][j] * b[i][j] - b[i + 1][j] * b[i + 1][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		int r = i;
		for (int j = i + 1; j <= n; j++)
			if (fabs(a[r][i]) < fabs(a[j][i])) r = j;
		if (i != r) for (int j = 1; j <= n + 1; j++) swap(a[i][j], a[r][j]);
		double tmp = a[i][i];
		for (int j = i; j <= n + 1; j++) a[i][j] /= tmp;
		for (int j = i + 1; j <= n; j++) {
			double tmp = a[j][i];
			for (int k = i; k <= n + 1; k++) a[j][k] -= a[i][k] * tmp;
		}
	}
	ans[n] = a[n][n + 1];
	for (int i = n - 1; i >= 1; i--) {
		ans[i] = a[i][n + 1];
		for (int j = i + 1; j <= n; j++) ans[i] -= a[i][j] * ans[j];
	}
	for (int i = 1; i <= n; i++) printf("%.3lf ", ans[i]);
	return 0;
}
posted @   Last_Breath  阅读(280)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示