CCF-CSP认证考试 202403-3 化学方程式配平 100分题解

更多 CSP 认证考试题目题解可以前往:CSP-CCF 认证考试真题题解


原题链接: 202403-3 化学方程式配平

时间限制: 1.0 秒
空间限制: 512 MiB

题目背景

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

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

题目描述

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

例如要配平化学方程式: Al 2 ( S O 4 ) 3 + N H 3 ⋅ H 2 O → Al ( OH ) 3 + ( N H 4 ) 2 S O 4 \text{Al}_2(\text{S}\text{O}_4)_3 + \text{N}\text{H}_3\cdot\text{H}_2\text{O} \rightarrow \text{Al}(\text{O}\text{H})_3 + (\text{N}\text{H}_4)_2\text{S}\text{O}_4 Al2(SO4)3+NH3H2OAl(OH)3+(NH4)2SO4

首先假定所有物质在方程的同一侧,即不考虑哪个是反应物,哪个是生成物,分别设这些物质的系数为 x 1 , x 2 , x 3 , x 4 x_1, x_2, x_3, x_4 x1,x2,x3,x4,则可以针对出现的各个元素,列出如下的方程组:
2 x 1 + 0 x 2 + x 3 + 0 x 4 = 0 Al 3 x 1 + 0 x 2 + 0 x 3 + x 4 = 0 S 12 x 1 + x 2 + 3 x 3 + 4 x 4 = 0 O 0 x 1 + x 2 + 0 x 3 + 2 x 4 = 0 N 0 x 1 + 5 x 2 + 3 x 3 + 8 x 4 = 0 H  \begin{align} 2x_1 + 0x_2 + x_3 + 0x_4 &= 0 && \text{Al} \\ 3x_1 + 0x_2 + 0x_3 + x_4 &= 0 && \text{S} \\ 12x_1 + x_2 + 3x_3 + 4x_4 &=0 && \text{O} \\ 0x_1 + x_2 + 0x_3 + 2x_4 &=0 && \text{N} \\ 0x_1 + 5x_2 + 3x_3 + 8x_4 &=0 && \text{H} \ \end{align} 2x1+0x2+x3+0x43x1+0x2+0x3+x412x1+x2+3x3+4x40x1+x2+0x3+2x40x1+5x2+3x3+8x4=0=0=0=0=0AlSONH 
用矩阵的形式表示为:

( 2 0 1 0 3 0 0 1 12 1 3 4 0 1 0 2 0 5 3 8 ) ⋅ ( x 1 x 2 x 3 x 4 ) = 0 \begin{pmatrix} 2 & 0 & 1 & 0 \\ 3 & 0 & 0 & 1 \\ 12 & 1 & 3 & 4 \\ 0 & 1 & 0 & 2 \\ 0 & 5 & 3 & 8 \end{pmatrix} \cdot \begin{pmatrix} x_1 \\ x_2 \\ x_3 \\ x_4 \end{pmatrix} = \mathbf{0} 231200001151030301428 x1x2x3x4 =0

对系数矩阵实施高斯消元,得到系数矩阵的一个行阶梯形式:

( 2 0 1 0 0 1 − 3 4 0 0 − 3 2 1 0 0 0 0 0 0 0 0 ) \begin{pmatrix} 2 & 0 & 1 & 0 \\ 0 & 1 & -3 & 4 \\ 0 & 0 & -{3\over2} & 1 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ \end{pmatrix} 200000100013230004100

由此可见,系数矩阵的秩为 3。根据线性代数的知识,我们知道,齐次线性方程组 A X = 0 \mathbf{A}\mathbf{X} = \mathbf{0} AX=0 的解空间的维数等于其未知数个数减去系数矩阵的秩 rank A \text{rank} \mathbf A rankA。而要让方程式配平,即要求方程组存在非零解, 那么就需要让解空间的维数大于 0 0 0,即系数矩阵的秩小于未知数个数。因此,我们可以通过判断系数矩阵的秩是否小于未知数个数,来判断方程式是否可以配平。如果可以配平,则可以通过解的符号来判断反应物和生成物的位置。

本题中,我们将给出一些化学方程式,请你按照上述方法判断它们是否可以配平。为了便于程序处理,我们用到的化学式,会被化简为只包含小写字母和数字的字符串,不包含括号。 其中连续的字母表示一种元素,随后的数字表示原子个数。原子个数为 1 时不省略数字;一个化学式中包含的元素不重复。例如,上述方程式中的化学式可以化简为 al2s3o12n1h5o1al1o3h3n2h8s1o4

输入格式

从标准输入读入数据。

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

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

输出格式

输出到标准输出。

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

样例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 个待配平的化学方程式,其中各方程式的配平情况为:

  • 3 O 2 = 2 O 3 3\text{O}_2 = 2\text{O}_3 3O2=2O3
  • 2 CO + O 2 = 2 C O 2 2\text{C}\text{O} + \text{O}_2 = 2\text{C}\text{O}_2 2CO+O2=2CO2
  • N 2 O 4 = 2 N O 2 \text{N}_2\text{O}_4 = 2\text{N}\text{O}_2 N2O4=2NO2
  • 因为缺少生成物 NO \text{N}\text{O} NO N O 2 \text{N}\text{O}_2 NO2,所以不可以配平
  • Al 2 ( S O 4 ) 3 + 6 N H 3 ⋅ H 2 O = 2 Al ( OH ) 3 + 3 ( N H 4 ) 2 S O 4 \text{Al}_2(\text{S}\text{O}_4)_3 + 6\text{N}\text{H}_3\cdot\text{H}_2\text{O} = 2\text{Al}(\text{O}\text{H})_3 + 3(\text{N}\text{H}_4)_2\text{S}\text{O}_4 Al2(SO4)3+6NH3H2O=2Al(OH)3+3(NH4)2SO4
  • 2 CO + O 2 = 2 C O 2 2\text{C}\text{O} + \text{O}_2 = 2\text{C}\text{O}_2 2CO+O2=2CO2​,本方程式对应的线性方程组求解后,得到 H 2 O \text{H}_2\text{O} H2O 的系数为 0,说明其未参与反应,属多余的物质。在这种情况下,由于对应的线性方程组存在非零解,所以我们仍然认为这个方程式是可以配平的。

子任务

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

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

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

提示

  • 对矩阵进行高斯消元的一种方法是:

    1. 考察矩阵的第一列上的元素:
      • 若全都为零,则对除去该列的子矩阵重复上述判断;
      • 若不全为零,则:
        1. 考察第一行第一列的元素:
          • 如果其为 0,则将该行与后面的某一个第一列非 0 的行交换,使第一行第一列的元素非 0;
        2. 令后续所有行减去第一行的适当倍数,使得后续所有行的第一列元素为 0;
    2. 对除去第一行第一列的子矩阵重复上述操作,直至不再余下子矩阵。
  • 对系数矩阵高斯消元后,不全为 0 的行的数目即为系数矩阵的秩。

  • 评测环境仅提供各语言的标准库,特别地,不提供任何线性代数库。


题解

先将每个物质的各个元素拆分开来。可以观察到每个物质的化学式中,都是按照元素和对应元素的个数的排列顺序,而元素都是英文字母,元素个数都是数字。

这样子可以先将元素与元素之间区分开来(即如果当前位为数字,下一位为字母,那么这两位之间必定是元素的分界线),然后再将元素名称和元素个数区分开来(对于区分好的元素,如果当前位为字母,下一位为数字,那么这两位之间必定是元素和个数的分界线)。

对于每个元素,可以使用 std::map 来为其设定编号(在系数矩阵中的行数,即对应题目中的第几个方程)。对于第 i i i 个物质中,元素编号为 j j j 的元素,将其个数存储在系数矩阵的第 j j j 行第 i i i 列。

系数矩阵构造完后,使用高斯消元的方法来求解行列式的秩,高斯消元的具体过程在提示中已经给出。

求出系数矩阵的秩后,判断秩 rank 和未知数个数(所涉及物质的数量)m 的关系,如果 rank < m,则化学方程式可以配平,输出 Y;否则,化学方程式无法配平,输出 N

时间复杂度: O ( n ⋅ 4 0 3 ) \mathcal{O}(n\cdot 40^3) O(n403)

参考代码

/*
    Created by Pujx on 2024/5/8.
*/
#pragma GCC optimize(2, 3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
//#define double long double
using i64 = long long;
using ui64 = unsigned long long;
using i128 = __int128;
#define inf (int)0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define yn(x) cout << (x ? "yes" : "no") << endl
#define Yn(x) cout << (x ? "Yes" : "No") << endl
#define YN(x) cout << (x ? "YES" : "NO") << endl
#define mem(x, i) memset(x, i, sizeof(x))
#define cinarr(a, n) for (int _ = 1; _ <= n; _++) cin >> a[_]
#define cinstl(a) for (auto& _ : a) cin >> _
#define coutarr(a, n) for (int _ = 1; _ <= n; _++) cout << a[_] << " \n"[_ == n]
#define coutstl(a) for (const auto& _ : a) cout << _ << ' '; cout << endl
#define all(x) (x).begin(), (x).end()
#define md(x) (((x) % mod + mod) % mod)
#define ls (s << 1)
#define rs (s << 1 | 1)
#define ft first
#define se second
#define pii pair<int, int>
#define eps 1e-8
#ifdef DEBUG
    #include "debug.h"
#else
    #define dbg(...) void(0)
#endif

const int N = 50 + 5;
//const int M = 1e5 + 5;
const int mod = 998244353;
//const int mod = 1e9 + 7;
//template <typename T> T ksm(T a, i64 b) { T ans = 1; for (; b; a = 1ll * a * a, b >>= 1) if (b & 1) ans = 1ll * ans * a; return ans; }
//template <typename T> T ksm(T a, i64 b, T m = mod) { T ans = 1; for (; b; a = 1ll * a * a % m, b >>= 1) if (b & 1) ans = 1ll * ans * a % m; return ans; }

double a[N][N];
int n, m, t, k, q;

int gauss(int n, int m) {
    int rnk = 0;
    for (int i = 1; i <= n; i++) {
        int t = rnk + 1;
        for (int j = rnk + 1; j <= n; j++) 
            if (a[j][i]) { t = j; break; }
        if (fabs(a[t][i]) < eps) continue;
        ++rnk;
        for (int j = i; j <= m; j++) swap(a[t][j], a[rnk][j]);
        for (int j = m; j >= i; j--) a[rnk][j] /= a[rnk][i];
        for (int j = rnk + 1; j <= n; j++)
            if (fabs(a[j][i]) > eps)
                for (int k = m; k >= i; k--)
                    a[j][k] -= a[rnk][k] * a[j][i];
    }
    return rnk;
}

void work() {
    cin >> n;
    vector<string> s(n);
    for (auto& ss : s) cin >> ss; 
    map<string, int> mp;
    int cnt = 0;
    memset(a, 0, sizeof(a));
    
    for (int i = 0; i < n; i++) {
        auto& ss = s[i];
        for (int l = 0, r = 0; l < ss.length(); r = l = r + 1) {
            auto isNum = [&] (char c) -> bool { return c >= '0' && c <= '9'; };
            while (r + 1 < ss.length() && !(isNum(ss[r]) && !isNum(ss[r + 1]))) r++;
            string cur = ss.substr(l, r - l + 1), elem;
            for (auto ch : cur) if (!isNum(ch)) elem += ch;
            int num = stoi(cur.substr(elem.length()));            
            int elemid = mp.count(elem) ? mp[elem] : (mp[elem] = ++cnt);
            a[elemid][i + 1] = num;
        }
    }
    int rnk = gauss(cnt, n);
    cout << "NY"[rnk < n] << endl;
}

signed main() {
#ifdef LOCAL
    freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.in", "r", stdin);
    freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int Case = 1;
    cin >> Case;
    while (Case--) work();
    return 0;
}
/*
     _____   _   _       _  __    __
    |  _  \ | | | |     | | \ \  / /
    | |_| | | | | |     | |  \ \/ /
    |  ___/ | | | |  _  | |   }  {
    | |     | |_| | | |_| |  / /\ \
    |_|     \_____/ \_____/ /_/  \_\
*/

关于代码的亿点点说明:

  1. 代码的主体部分位于 void work() 函数中,另外会有部分变量申明、结构体定义、函数定义在上方。
  2. #pragma ... 是用来开启 O2、O3 等优化加快代码速度。
  3. 中间一大堆 #define ... 是我习惯上的一些宏定义,用来加快代码编写的速度。
  4. "debug.h" 头文件是我用于调试输出的代码,没有这个头文件也可以正常运行(前提是没定义 DEBUG 宏),在程序中如果看到 dbg(...) 是我中途调试的输出的语句,可能没删干净,但是没有提交上去没有任何影响。
  5. ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); 这三句话是用于解除流同步,加快输入 cin 输出 cout 速度(这个输入输出流的速度很慢)。在小数据量无所谓,但是在比较大的读入时建议加这句话,避免读入输出超时。如果记不下来可以换用 scanfprintf,但使用了这句话后,cinscanfcoutprintf 不能混用。
  6. main 函数和 work 函数分开写纯属个人习惯,主要是为了多组数据。
### 回答1: CCF-CSP(中国计算机学会认证系统程序员)认证考试是由中国计算机学会主办的,旨在选拔高水平的系统程序员和开发人员。该认证考试包括两个级别:初级和高级。初级考试主要测试候选人的基础知识和编程能力,高级考试则主要测试候选人的系统设计和开发能力。CCF-CSP认证考试的评标准严格,要求考生表现出扎实的计算机理论基础和熟练的编程技巧。 为了顺利通过CCF-CSP认证考试,考生需要掌握扎实的计算机基础知识和熟练的编程技巧,并且需要在考试前进行充的准备。以下是一些复习技巧指导: 1. 确认考试大纲 考生需要仔细阅读考试大纲,了解考试范围和题型。要特别注意考试大纲中的重点内容,重点复习。 2. 提前规划复习进度 准备CCF-CSP认证考试需要时间,而时间是很宝贵的,考生需要提前规划好复习进度和复习内容,尽可能利用零散时间进行复习。 3. 多做题 要想掌握CCF-CSP认证考试所需的知识和技能,考生需要多做题,并针对性地练习一些经典的考题。在练习中,考生需要注重练习思路和解题方法,该方法可以加深对知识点的理解。 4. 关注考试动态 考试动态指的是考试历年来的趋势和变化,考生需要不断关注考试动态,了解考试趋势,及时更新复习内容和方法,以便更好地应对考试。 总之,要想通过CCF-CSP认证考试,考生需要理论基础和开发经验充足,并且需要在复习时切实遵循学科规范,灵活应用知识点,较好地掌握考试技巧,这是顺利通过CCF-CSP认证考试的关键。 ### 回答2: ccf-csp认证考试是中国计算机学会(CCF认证的一个计算机软件专业技能等级考试,也是中国IT行业里非常重要的证书之一。该考试为初级、中级和高级三个等级,其中初级考试包含了基础的程序设计和算法等内容,中级考试中主要考察了软件工程、数据结构、算法设计和网络安全等领域的知识,而高级考试则更注重的是对软件工程和软件项目管理的理解和应用,以及对复杂系统的设计和实现方法的掌握。为了能够成功通过ccf-csp认证考试,首先需要在考前充准备,并且需要有一些好的复习技巧,这里向大家推荐以下几点: 1.学好基础知识。初级、中级和高级csp认证考试所涉及到的内容都是建立在一定的基础知识上的。由于考试的难度逐级递增,所以建议在准备考试之前先花时间学好基础知识。 2.多做练习题。做练习题能够帮助我们加深对知识的理解,熟悉考试形式和规则,同时也能提高我们的答题速度和准确度。 3.注意时间管理。不同级别的csp认证考试都有时限,在考试过程中需要注意时间的配。建议在每次做练习题的时候都设置一个时间限制,并逐步缩短时间限制,以提高答题效率。 4.了解考试规则。在考前一定要熟悉考试规则和题型,知道考试时可以携带哪些资料和工具,了解考试的答题和评规则等。 总之,ccf-csp认证考试是一项非常重要的认证考试,通过这个认证不仅可以增加个人的职业竞争力,还能证明个人在软件开发和IT行业方面的专业能力。因此,我们需要认真地准备和复习,字斟句酌地做好每一个细节,以确保顺利通过考试。 ### 回答3: ccf-csp认证考试是由中国计算机学会主办的全国性计算机考试考试内容涵盖了计算机应用、技术、算法等多个方面,为初级、中级和高级三个等级。参加此考试不仅有助于检测个人的计算机专业水平,更能提升自身的竞争力和职业发展前景。 为了通过ccf-csp认证考试,考生需要重点复习考试要求中的各个内容模块,如数据类型、运算符、控制结构、数组、函数、文件操作等程序设计相关方面;还包括计算机网络、操作系统、数据库等计算机技术类知识;另外,还需要掌握常见算法和数据结构,如排序、查找、树等。此外,考生还需要通过大量的编程练习和模拟题练习,熟悉考试题型和答题技巧,提升自己的完成效率和准确性。 在复习过程中,需要注重细节和实践。例如,对于程序设计相关方面,需要理解每个知识点的原理和应用场景,在编写代码时要注重细节避免出现错误。对于算法和数据结构方面,需要熟悉常见算法的实现方式和优缺点,掌握好算法的时间复杂度和空间复杂度,做到熟练掌握。 总之,通过充准备和钻研,考生有望成功通过ccf-csp认证考试,展现自己的计算机专业能力和职业素养。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值