高斯消元

高斯消元是一类用来求解 线性方程组(\(n\) 元一次方程组) 的算法


Gauss 消元

核心思想其实就是代入消元

我们考虑每次将一个未知数的所有系数消为 \(0\),只留下一个方程的这位系数不为 \(0\)

步骤如下:设当前消到第 \(i\) 个未知数

  • \(i\) 个方程开始找,找到第一个不为第 \(i\) 项的系数不为 \(0\) 的方程 \(id\),退出循环,交换第 \(i\) 个方程 and 第 \(id\) 个方程

  • 然后对 \([i,n]\) 的所有方程进行消元,设目前处理到第 \(j\) 个方程,则令 \(mul = a[j][i] / a[i][i]\),对方程 \(j\) 的每个项的系数 \(k\ (k \ge i)\) 都减去 \(a[i][k] * mul\)

这就是加减消元的过程

但由于我们只消掉了一半的未知数(因为每次都从第 \(i\) 个方程往后消,前面的没有消到),所以它会长这样(假设有解)

\[\begin{aligned} \begin{matrix} a_{1,1} & a_{1,2} & a_{1,3} & ... & a_{1,n-1} & a_{1,n} & b_1 \\ 0 & a_{2,2} & a_{2,3} & ... & a_{2,n-1} & a_{2,n} & b_2 \\ 0 & 0 & a_{3,3} & ... & a_{3,n-1} & a_{3,n} & b_3 \\ &&&... \\ 0 & 0 & 0 & ... & 0 & a_{1,n} & b_n \\ \end{matrix} \end{aligned}\]

(其中 \(a\) 表示系数,\(b\) 表示常数)

因此我们还要回代,也就是先求出第 \(n\) 个未知数 \(x_n=b_n/a_{n,n}\)

然后第 \(n-1\) 个方程的常数 \(b_{n-1}\) 减去 \(a_{n-1,n}\times x_n\),然后再求出第 \(n-1\) 个未知数 \(x_{n-1}=b_{n-1}/a_{n-1,n-1}\)

然后第 \(n-2\) 个方程的常数 \(b_{n-2}\) 减去 \(a_{n-2,n}\times x_n+a_{n-2,n-1}\times x_{n-1}\),然后再求出第 \(n-2\) 个未知数 \(x_{n-2}=b_{n-2}/a_{n-2,n-2}\)

...

以此类推,最后求出第 \(1\) 个未知数 \(x_1\)

至于无解:就是在消元的过程中找不到系数不为 \(0\) 的项,代表当前的未知数无解

这种消元方法的代码

时间复杂度高达 \(O(n^3)\)


高斯-约旦消元

听说这玩意的精度更好,因为不会产生回代的计算误差(也不知道是不是真的)

但根据 lby 大佬说的:

你这方法的常数比普通的 Gauss 消元大一倍呢

高斯-约旦消元,就是一种不需要回代的消元方法

它与普通的 Gauss消元 的区别就在于,第二步中我们将对 \([1,n]\) 但不包括第 \(i\) 个的方程都进行消元(其他步骤相同)

这样我们第 \(i\) 个方程就只有第 \(i\) 项上是有系数的

答案直接输出 \(b_i/a_{i,i}\) 即可

这种消元方法的代码


带状矩阵消元

这在 有后效性的期望DP 应用比较多

有时候,我们通过对系数进行一定调整,会发现我们需要消掉的系数会长这样:

BM

橙色部分就是我们要消去的部分

考虑用普通的 Gauss消元,我们可以发现,每次我们最多只需要向下消 \(d\) 个方程

而且每个方程我们也只需要消去 \([i,i+d]\) 上面的系数即可,因为第 \(i\) 个方程 \(i+d\) 后面的系数都为 \(0\),消了和没消一样

这样我们就可以做到 \(O(nd^2)\) 进行消元

(记得要回代!)

关键部分(加减消元)的代码:

for(int i = 1; i <= n; i++)
    {
        for(int j = i + 1; j <= std::min(i + d, n); j++)
        {
            double mul = a[j][i] / a[i][i];
            for(int k = i; k <= std::min(i + d, n); k++)
                a[j][k] -= a[i][k] * mul;
            a[j][n + 1] -= a[i][n + 1] * mul; //记得这里要把常数也减一下!
        }
    }

但有一个棘手的问题,如果处理到第 \(i\) 个未知数时,第 \(i\) 个方程的系数 \(a_{i,i}\)\(0\) ,我们就要进行交换,但一交换就会破坏带状结构,这样就无法做到 \(O(nd^2)\)

但实际上,对应 DP 这类问题,这种情况是不存在的,可以证明,参考这里

但其实还有一种方法,我们考虑交换后会变成什么情形:

(借用 Froggy 大佬的图)

最坏情况下,我们交换后会让带的宽扩展 \(d\)

那其实我们在消系数的时候一直消到 \(i+d*2\) 不就行了

也就多一倍的常数而已

因此我们可以这样做:

for(int i = 1; i <= n; i++)
    {
        int id = i;
        for(int j = i; j <= std::min(i + d, n); j++)
            if(a[j][i])
            {
                id = j;
                break;
            }
        std::swap(a[i], a[id]);
        if(!a[i]) continue;
        for(int j = i + 1; j <= std::min(i + d, n); j++)
        {
            double mul = a[j][i] / a[i][i];
            for(int k = i; k <= std::min(i + (d << 1), n); k++)
                a[j][k] -= a[i][k] * mul;
            a[j][n + 1] -= a[i][n + 1] * mul;
        }
    }

主元法

这种方法有一定的要求:在网格图中,且边界外的期望值已知(一般为 \(0\)

可以将 \(O((nm)^3)\) 的普通高消优化到 \(O(n^3)\)\(O(m^3)\)

问题引入:

在一个 \(n\times m\) 的网格图中,人物初始位置为 \((x,y)\),它每次可以上下左右移动,概率为 \(p_1,p_2,p_3,p_4\),问它走到边界外的期望步数(到边界外即停止)

如果 \(n,m\) 小一点,我们就可以根据朴素的高消做:

\(f_{i,j} = f_{i-1,j}p_1+f_{i+1,j}p_2+f_{i,j-1}p3+f_{i,j+1}p4+1\)

但如果 \(n,m\le 100\) 呢?

显然这会直接炸掉

但我们注意到,边界外的期望始终为 \(0\)

那我们就考虑通过只设一列为未知数,剩下的格子由这些未知数来表示

\(5\times 4\) 的图:

我们将第一列依次设为 \(x_1,x_2,x_3,x_4\)

那么因为 \(x_1(f_{1,1})=f_{0,1}p_1+f_{2,1}p_2+f_{1,0}p3+f_{1,2}p4+1=x_2p_2+f_{1,2}+1\)

因此 \(f_{1,2}=\frac{x_1-x_2p_2-1}{p_4}\)

剩下的依次类推,每次都是一列一列地向右推进

到最后时,我们表示出第 \(m+1\) 列的格子,由于这些格子已经在边界外了,也就是它们就为 \(0\),这样我们就能得到 \(n\) 个方程

这样我们只需要对我们设的未知数进行高斯消元即可,最后代入到 \(f_{x,y}\) 的表达式算出答案即可

那如果对于每次可以向八个方向移动的题呢?那我们直接对第一行和第一列都设成未知数即可,然后就去表示即可

posted @ 2022-03-30 22:09  zuytong  阅读(232)  评论(0编辑  收藏  举报