zoukankan      html  css  js  c++  java
  • [算法] 高斯消元及其应用

    主要问题

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

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

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

    解决方法

    高斯消元法的思路是:通过消元运算,直到得到一个只关于 \(x_1\) 的式子,只关于 \(x_1,x_2\) 的式子,只关于 \(x_1,x_2,x_3\) 的式子,然后回代计算即可。

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

    \[\begin{cases} a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n=b_1 \\ a_{2,1}x_1+a_{2,2}x_2+...+a_{2,n}x_n=b_2 \\ \vdots \\ a_{n,1}x_1+a_{n,2}x_2+...+a_{n,n}x_n=b_n \\ \end{cases} \]

    将上述方程转换为

    \[\begin{cases} a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n=b_1 \\ a_{1,1}x_1+\frac{a_{2,2}a_{1,1}}{a_{2,1}}x_2+...+\frac{a_{2,n}a_{1,1}}{a_{2,1}}x_n=b_2 \\ \vdots \\ a_{1,1}x_1+\frac{a_{n,2}a_{1,1}}{a_{n,1}}x_2+...+\frac{a_{n,n}a_{1,1}}{a_{n,1}}x_n=b_n \\ \end{cases} \]

    最后通过减法消去后 \(n-1\) 个方程的 \(x_1\)

    \[\begin{cases} a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n=b_1 \\ (\frac{a_{2,2}a_{1,1}}{a_{2,1}}-a_{1,1})x_2+...+(\frac{a_{2,n}a_{1,1}}{a_{2,1}}-a_{1,n})x_n=b_2-b_1 \\ \vdots \\ (\frac{a_{n,2}a_{1,1}}{a_{n,1}}-a_{1,1})x_2+...+(\frac{a_{n,n}a_{1,1}}{a_{n,1}}-a_{1,n})x_n=b_n-b_1 \\ \end{cases} \]

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

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

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

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

    实现中还有一个问题,每次求解的 \(a_{1,1}\) 是否会对答案产生影响?

    可以发现,若 \(a_{1,1}\) 太小,那么会导致精度问题,所以每次需要选取主元素:最大的 \(a_{i,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\) 的坐标为 \((a_1, a_2, \cdots , a_n), (b_1, b_2, \cdots , b_n)\),则AB的距离定义为:\(dist = \sqrt{ (a_1-b_1)^2 + (a_2-b_2)^2 + \cdots + (a_n-b_n)^2 }\)

    思路

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

    \(\sum_{j=0}^{n} (a_{i,j}-x_j)^2=C\)

    化简得:

    \(\sum_{j=1}^{n}(a_{i,j}^2-a_{i+1,j}^2-2x_j(a_{i,j}-a_{i+1,j}))=0\)

    将常数项与未知数分离:

    \(\sum_{j=1}^{n}2(a_{i,j}-a_{i+1,j})x_j=\sum_{j=1}^{n}(a_{i,j}^2-a_{i+1,j}^2)\)

    等价转化为了 \(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;
    }
    
  • 相关阅读:
    每天一个linux命令(1):ls命令
    如何查看和停止Linux启动的服务
    JavaScript作用域原理——作用域根据函数划分
    iOS 自动布局详细介绍
    arc下内存泄漏的解决小技巧
    AFNetwork2.0在报错1016,3840的解决方法及一些感悟
    iOS聊天下拉刷新聊天记录的实现
    tableview直接滚动至最后一行
    UITabBar,UINavigationBar的布局和隐藏问题
    transformjs玩转星球
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/15793297.html
Copyright © 2011-2022 走看看