化学方程式配平

解析每种物质的元素计数,建立元素-物质矩阵并用高斯消元判断其秩是否小于未知数个数。

OJ: shumeng

题目 ID: CSP202403C

难度:普及+/提高-

标签:字符串解析线性代数高斯消元

日期: 2026-07-31 16:21

形式化题目

给定 mm 种物质的化学式(已去掉括号,只含连续小写字母元素名和其后数字),把每种物质看作方程同一侧的未知系数,判断对应的齐次线性方程组

AX=0AX = 0

是否存在非零解。存在则输出 Y,否则输出 N

其中 AA 是元素-物质计数矩阵,A[i][j]A[i][j] 表示第 jj 种物质中元素 ii 的原子个数。

思路

问题的数学本质:AX=0AX = 0 有非零解当且仅当 rank(A)<m\operatorname{rank}(A) < m(未知数个数)。所以核心工作只有两步:把化学式转成矩阵,再求矩阵的秩。

解析化学式

化学式只含连续小写字母和数字,例如 al2s3o12 表示 al 有 2 个、s 有 3 个、o 有 12 个。从左到右扫描:读出一串小写字母得到元素名,再读出一串数字得到原子个数,存入 map<元素, 个数>

构造矩阵

每种出现的元素对应一行,每种物质对应一列。用 map<string, int> 给元素分配行号,把每种物质的原子个数填入对应行列。

高斯消元求秩

对矩阵做列主元高斯消元:每一列找一个绝对值最大的非零元素作主元,换到当前行并用它消去下方各行;找不到非零主元的列跳过。消元后非零行数就是矩阵的秩。

判断标准:秩小于物质个数 mm 时方程组有自由变量,存在非零解,输出 Y

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 50;

int substance_count;          // 一个方程中物质的个数(矩阵列数)
int element_count;            // 一个方程中出现的元素种类数(矩阵行数)
map<string, int> element_id;  // 元素名称 -> 矩阵行号
long double matrix[MAXN][MAXN]; // matrix[i][j]:第 j 个物质中含元素 i 的原子个数

// 解析化学式:连续小写字母为元素名,其后紧跟的数字为该元素的原子个数
map<string, int> parse_formula(const string &formula) {
    map<string, int> result;
    int position = 0;
    while (position < (int)formula.size()) {
        string element;
        while (position < (int)formula.size()
                && 'a' <= formula[position] && formula[position] <= 'z') {
            element += formula[position++];
        }
        int number = 0;
        while (position < (int)formula.size()
                && '0' <= formula[position] && formula[position] <= '9') {
            number = number * 10 + formula[position++] - '0';
        }
        result[element] = number;
    }
    return result;
}

// 读入一个方程的各个物质,构造元素-物质计数矩阵
void read_equation() {
    cin >> substance_count;
    element_id.clear();
    memset(matrix, 0, sizeof(matrix));

    for (int column = 0; column < substance_count; column++) {
        string formula;
        cin >> formula;
        map<string, int> counts = parse_formula(formula);
        for (map<string, int>::iterator it = counts.begin(); it != counts.end(); ++it) {
            int row;
            if (element_id.count(it->first)) {
                row = element_id[it->first];
            } else {
                row = (int)element_id.size();
                element_id[it->first] = row;
            }
            matrix[row][column] = it->second;
        }
    }
    element_count = (int)element_id.size();
}

// 高斯消元(列主元)求矩阵的秩,返回非零行数
int matrix_rank() {
    int rank = 0;
    for (int column = 0; column < substance_count && rank < element_count; column++) {
        // 在当前列选择绝对值最大的行作为主元,提高数值稳定性
        int pivot = rank;
        for (int row = rank + 1; row < element_count; row++) {
            if (fabsl(matrix[row][column]) > fabsl(matrix[pivot][column])) pivot = row;
        }
        // 主元为 0 说明该列已是自由列,直接跳过
        if (fabsl(matrix[pivot][column]) < 1e-12L) continue;

        // 把主元行换到当前行
        for (int j = column; j < substance_count; j++) {
            swap(matrix[pivot][j], matrix[rank][j]);
        }
        // 用当前行消去下面各行的当前列
        for (int row = rank + 1; row < element_count; row++) {
            if (fabsl(matrix[row][column]) < 1e-12L) continue;
            long double ratio = matrix[row][column] / matrix[rank][column];
            for (int j = column; j < substance_count; j++) {
                matrix[row][j] -= ratio * matrix[rank][j];
            }
        }
        rank++;
    }
    return rank;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int equation_count;
    cin >> equation_count;
    while (equation_count--) {
        read_equation();
        // 齐次方程组 AX=0 有非零解当且仅当矩阵秩小于未知数个数
        cout << (matrix_rank() < substance_count ? 'Y' : 'N') << '\n';
    }

    return 0;
}

复杂度

设一个方程中物质个数为 mm,涉及的元素种类数为 eem,e40m, e \le 40)。

  • 时间:解析每个化学式为 O(公式长度)O(\text{公式长度});高斯消元 O(emmin(e,m))O(e \cdot m \cdot \min(e, m))
  • 空间:元素-物质矩阵大小为 e×me \times m,空间复杂度 O(em)O(em)

总结

这道题把化学背景完全剥离后就是一个线性代数问题:判断齐次方程组是否存在非零解等价于比较矩阵秩与未知数个数。难点在于把化学式干净地转成计数矩阵,以及数值消元时选择绝对值最大的主元来保证精度。