题解 P2455 【[SDOI2006]线性方程组】

· · 题解

由线性代数知识,我们知道,对于n元线性方程组\boldsymbol{AX}=\boldsymbol{\beta},它的解的情况为:

然后我们直接模拟即可,用高斯-约旦消元法把增广矩阵化成行最简形矩阵。

我这里写了一个class封装了一下,利用了STL中的valarray。另外代码中含有不少C++11+的特性,在NOIP等环境需要把auto换成相应的类型。

template <typename T>
class mat
{
    int r, c; // 行,列
    bool simplified = false; // 记录是否已化简
    valarray<T> data; // 用valarray存储数据

public:
    mat(int r, int c) : r(r), c(c), data(r * c) {}
    // M(i, j)可取第i行第j列的元素
    T &operator()(int i, int j) { return data[i * c + j]; }
    // M[i]可取第i行的切片
    auto operator[](int i) { return data[slice(i * c, c, 1)]; }
    // M(i)可取第i列的切片
    auto operator()(int i) { return data[slice(i, r, c)]; }
    // 交换两行
    void rswap(int r1, int r2)
    {
        for (int i = 0; i < c; ++i)
            swap((*this)(r1, i), (*this)(r2, i));
    }
    // 接下来是重点,进行行变换,把矩阵变成行最简形矩阵
    mat<T> &simplify()
    {
        int curr = 0; // 当前行
        auto &M = *this;
        for (int i = 0; i < c && curr < r; ++i) // 枚举列
        {
            int maxr = curr; // 找到该列(剩余的)最大元素所在的行
            for (int j = curr + 1; j < r; ++j)
                if (M(j, i) > M(maxr, i))
                    maxr = j;
            if (M(maxr, i) == 0) // 该列剩余元素全为0,跳到下一列
                continue;
            rswap(maxr, curr); // 把最大元素所在行交换到当前行处
            M[curr] /= valarray<T>(M(curr, i), c); // 令最大元素归一
            valarray<T> line = M[curr];
            for (int j = 0; j < r; ++j) // 用当前行对其余行进行消元
                if (j != curr)
                    M[j] -= line * M(j, i);
            curr++;
        }
        simplified = true;
        return *this;
    }
    // 从第L列到第R列(左闭右开)的子矩阵的秩
    int rank(int L, int R)
    {
        // 如果还未化为行最简形矩阵,就复制一下当前矩阵,并化简
        mat &M = (simplified ? *this : mat(*this).simplify());
        int cnt = 0;
        for (int i = 0; i < r; ++i) // 统计非0行
        {
            bool allzero = true;
            for (int j = L; j < R; ++j)
                if (M(i, j) != 0)
                    allzero = false;
            cnt += !allzero;
        }
        return cnt;
    }
    int rank() { return rank(0, c); }
};

主函数就不写了,按照上面给出的定理很容易实现。只是有个细节可以注意一下:输出的时候可能会输出-0.00,所以可以把要输出的数加上一个很小的实数(如1e-6)再输出。