CCF CSP | 化学方程式配平

第33次CCF计算机软件能力认证

题目背景

近日来,西西艾弗岛化学研究中心的研究员们向岛上的初中学生开展了化学科普活动。在活动中发现,初学化学的同学们十分苦恼于正确配平化学方程式。 而还有一些同学,则提出了一些稀奇古怪的方程式,让研究员们帮忙配平。在配平之前,研究员们需要先判断这个方程式是否能够配平。

一个化学方程式,也叫化学反应方程式,是用化学式表示化学反应的式子。其等号左右两侧分别列举了化学反应的全部反应物和生成物。 每种物质都用其化学式表示。一个物质的化学式,列举了构成该物质的各元素的原子数目。例如,水的化学式是\(H_{2}O\)表示水分子中含有两个氢原子和一个氧原子。 化学方程式中每种物质的化学式前面都有一个系数,表示参与反应或生成的物质的相对数目比例。例如,方程式\(2H_{2}+O_{2}=2H_{2}O\) 表示二分子氢气和一分子氧气反应生成二分子水。 我们称一个化学方程式是配平的,是指该方程式中的反应物和生成物中,各元素原子总数目相等。例如上述方程式中,左侧氢原子、氧原子的总数目分别为4和2,右侧氢原子、氧原子的总数目分别为 4 和 2,因此该方程式是配平的。

题目描述

为了配平一个化学方程式,我们可以令方程式中各物质的系数为未知数,然后针对涉及的每一种元素,列出关于系数的方程,形成一个齐次线性方程组。然后求解这个方程组,得到各物质的系数。这样,我们就把化学方程式配平的问题,转化为了求解齐次线性方程组的问题。 如果方程组没有非零解,那么这个方程式是不可以配平的。反之,如果方程组有非零解,我们就可能得到一个配平的方程式。当然,最终得到的方程式仍然需要结合化学知识进行检验,对此我们不再进一步考虑,仅考虑非零解的存在。

例如要配平化学方程式:
\(Al_{2}(SO_{4})_{3}+NH_{3}\cdot H_{2}O\to Al(OH)_{3}+(NH_{4})_{2}SO_{4}\)首先假定所有物质在方程的同一侧,即不考虑哪个是反应物,哪个是生成物,分别设这些物质的系数为 x1,x2,x3,x4 ,则可以针对出现的各个元素,列出如下的方程组:
方程组
用矩阵的形式表示为:
矩阵
对系数矩阵实施高斯消元,得到系数矩阵的一个行阶梯形式:
行阶梯
由此可见,系数矩阵的秩为 3。根据线性代数的知识,我们知道,齐次线性方程组 \(AX=0\)的解空间的维数等于其未知数个数减去系数矩阵的秩 \(rankA\)。而要让方程式配平,即要求方程组存在非零解, 那么就需要让解空间的维数大于 0,即系数矩阵的秩小于未知数个数。因此,我们可以通过判断系数矩阵的秩是否小于未知数个数,来判断方程式是否可以配平。如果可以配平,则可以通过解的符号来判断反应物和生成物的位置。
本题中,我们将给出一些化学方程式,请你按照上述方法判断它们是否可以配平。为了便于程序处理,我们用到的化学式,会被化简为只包含小写字母和数字的字符串,不包含括号。 其中连续的字母表示一种元素,随后的数字表示原子个数。原子个数为 1 时不省略数字;一个化学式中包含的元素不重复。例如,上述方程式中的化学式可以化简为al2s3o12,n1h5o1,al1o3h3,n2h8s1o4

输入格式

从标准输入读入数据。

输入的第一行包含一个正整数 n,表示需要判断的化学方程式的个数。

接下来的 n 行,每行描述了一个需要被配平的化学方程式。包含空格分隔的一个正整数和全部涉及物质的化学式。其中,正整数 m 表示方程式中的物质;随后的 m 个字符串,依次给出方程式中的反应物的化学式和生成物的化学式。

输出格式

输出到标准输出。

输出包含 n 行,每行包含字母 Y 或 N,表示按题设方法,所给待配平化学方程式能否配平。

样例1输入

6
2 o2 o3
3 c1o1 c1o2 o2
2 n2o4 n1o2
4 cu1 h1n1o3 cu1n2o6 h2o1
4 al2s3o12 n1h5o1 al1o3h3 n2h8s1o4
4 c1o1 c1o2 o2 h2o1

样例1输出

Y
Y
Y
N
Y
Y

样例1解释

输入中给出了 5 个待配平的化学方程式,其中各方程式的配平情况为:

  • \(3O_{2}​=2O_{3}\)
  • \(2CO+O_{2}=2CO_{2}\)
  • \(N_{2}O_{4}​=2NO_{2}\)
  • 因为缺少生成物 \(NO\) 或 \(NO_{2}\),所以不可以配平
  • \(Al_{2}(SO_{4})_{3}​+6NH_{3} \cdot H_{2}O=2Al(OH)_{3}+3(NH_{4})_{2}SO_{4}\)
  • \(2CO+O_{2}=2CO_{2}\),本方程式对应的线性方程组求解后,得到 \(H_{2}O\) ,的系数为\(0\) ,说明其未参与反应,属多余的物质。在这种情况下,由于对应的线性方程组存在非零解,所以我们仍然认为这个方程式是可以配平的。

子任务

对于 20% 的数据,每个方程中物质的个数不超过 2,每个方程中涉及的全部元素不超过 2 种;

对于 60% 的数据,每个方程中物质的个数不超过 3,每个方程中涉及的全部元素不超过 3 种;

对于 100% 的数据,每个方程中物质的个数不超过 ,每个方程中涉及的全部元素不超过 40 种;且有 \(1≤n≤10\),且化学式中各元素的原子个数不超过 50。

提示

  • 对矩阵进行高斯消元的一种方法是:
    • 1.考察矩阵的第一列上的元素:
      • 若全都为零,则对除去该列的子矩阵重复上述判断;
      • 若不全为零,则:
        • 1.考察第一行第一列的元素:
          如果其为 0,则将该行与后面的某一个第一列非 0 的行交换,使第一行第一列的元素非 0;
        • 2.令后续所有行减去第一行的适当倍数,使得后续所有行的第一列元素为 0;
    • 2.对除去第一行第一列的子矩阵重复上述操作,直至不再余下子矩阵。
  • 对系数矩阵高斯消元后,不全为 0 的行的数目即为系数矩阵的秩。
  • 评测环境仅提供各语言的标准库,特别地,不提供任何线性代数库。

代码实现

#include<iostream>
#include<stdio.h>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int n,m;
void jiexi(string& t,vector<vector<float>>& v,int k,vector<string>& chem)
{
    int l=t.length();
    int i=0;
    while(i<l)
    {
        string a="";
        while(t[i]>='a'&&t[i]<='z')
        {
            a+=t[i];
            i++;
        }
        int sum=0;
        while(t[i]>='0'&&t[i]<='9')
        {
            sum*=10;
            sum+=t[i]-'0';
            i++;
        }
        //cout<<a<<" "<<sum<<endl;
        int j=0;
        for(;j<chem.size();j++)
        {
            if(chem[j]==a)
            {
                v[j][k]=sum;
                break;
            }
        }
        if(j==chem.size())
        {
            chem.push_back(a);
            vector<float> v0(m,0);
            v0[k]=sum;
            v.push_back(v0);
        }
    }
}
void Cal(vector<vector<float>>&);
void test1(vector<vector<float>>&);
void test2(vector<string>& chem);
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&m);
        vector<vector<float>> v;
        vector<string> chem;
        for(int j=0;j<m;j++)
        {
            string t;
            cin>>t;
            jiexi(t,v,j,chem);
        }
        Cal(v);
    }
    return 0;
}
void Cal(vector<vector<float>>& v)
{
    for(int i=0;i<m;i++)
    {
        int j;
        for(j=i;j<v.size();j++)
        {
            if(v[j][i]!=0)
            {
                swap(v[j],v[i]);
                break;
            }
        }
        if(j==v.size())continue;
        for(j=i+1;j<v.size();j++)
        {
            if(v[j][i]!=0)
            {
                double b=(double)v[j][i]/v[i][i];
                for(int k=i;k<m;k++)
                {
                    v[j][k]-=v[i][k]*b;
                }
            }
        }
    }
    int num=v.size();
    for(int j=0;j<v.size();j++)
    {
        bool flag=true;
        for(int i=0;i<m;i++)
        {
            if(v[j][i]!=0)
            {
                flag=false;
                break;
            }
        }
        if(flag)
        {
            num=j;break;
        }
    }
    //test1(v);
    //cout<<num<<" "<<m<<" ";
    if(num<m)cout<<"Y"<<endl;
    else cout<<"N"<<endl;
}
void test1(vector<vector<float>>& v)
{
    for(int j=0;j<v.size();j++)
    {
        for(int i=0;i<m;i++)
        {
            cout<<v[j][i]<<" ";
        }
        cout<<endl;
    }
}
void test2(vector<string>& chem)
{
    for(int k=0;k<chem.size();k++)
    {
        cout<<chem[k]<<endl;
    }
}
posted on 2025-06-08 20:42  Mayne  阅读(104)  评论(0)    收藏  举报