题目地址:
https://www.acwing.com/problem/content/3287/
化学方程式,也称为化学反应方程式,是用化学式表示化学反应的式子。给出一组化学方程式,请你编写程序判断每个方程式是否配平(也就是方程式中等号左右两边的元素种类和对应的原子个数是否相同)。本题给出的化学方程式由大小写字母、数字和符号(包括等号 =、加号 +、左圆括号(和右圆括号))组成,不会出现其他字符(包括空白字符,如空格、制表符等)。
化学方程式的格式与化学课本中的形式基本相同(化学式中表示元素原子个数的下标用正常文本,如
H
2
O
H_2O
H2O写成
H
2
O
H2O
H2O),用自然语言描述如下:
- 化学方程式由左右两个表达式组成,中间用一个等号
=
=
=连接,如
2H2+O2=2H2O
; - 表达式由若干部分组成,每部分由系数和化学式构成,部分之间用加号
+
+
+连接,如
2H2+O2
、2H2O
; - 系数是整数或空串,如为空串表示系数为 1 1 1;
- 整数由一个或多个数字构成;
- 化学式由若干部分组成,每部分由项和系数构成,部分之间直接连接,如
H2O
、CO2
、Ca(OH)2
、Ba3(PO4)2
;
项是元素或用左右圆括号括起来的化学式,如H
、Ca
、(OH)
、(PO4)
; - 元素可以是一个大写字母,也可以是一个大写字母跟着一个小写字母,如
H
、O
、Ca
。
用巴科斯范式(Backus-Naur form,BNF)给出的形式化定义如下:
<equation> ::= <expr> "=" <expr>
<expr> ::= <coef> <formula> | <expr> "+" <coef> <formula>
<coef> ::= <digits> | ""
<digits> ::= <digit> | <digits> <digit>
<digit> ::= "0" | "1" | ... | "9"
<formula> ::= <term> <coef> | <formula> <term> <coef>
<term> ::= <element> | "(" <formula> ")"
<element> ::= <uppercase> | <uppercase> <lowercase>
<uppercase> ::= "A" | "B" | ... | "Z"
<lowercase> ::= "a" | "b" | ... | "z"
输入格式:
输入的第一行包含一个正整数
n
n
n,表示输入的化学方程式个数。
接下来
n
n
n行,每行是一个符合定义的化学方程式。
输出格式:
输出共
n
n
n行,每行是一个大写字母Y
或N
,回答输入中相应的化学方程式是否配平。
数据范围:
1
≤
n
≤
100
1≤n≤100
1≤n≤100,
输入的化学方程式都是符合题目中给出的定义的,且长度不超过
1000
1000
1000。
系数不会有前导零,也不会有为零的系数。
化学方程式的任何一边,其中任何一种元素的原子总个数都不超过
1
0
9
10^9
109。
先将等号找出来,然后将左右两边截取出来。我们开一个函数来处理表达式,处理返回各个元素对应的个数构成的哈希表,然后比较两边表达式对应的哈希表即可。问题即归结为怎么处理表达式。由于表达式可能是一个化学式,也可能是化学式用加号连接,所以在找到加号位置的时候,就可以处理化学式,问题就变为怎么将化学式处理为一个哈希表。
先开个哈希表存储答案。对于一个化学式的处理可以用DFS的思路。首先化学式前面的系数要先截取出来,如果系数为空则视为
1
1
1。接着开始处理后面的部分。
如果遇到了左括号,则递归处理后面的部分,直到该左括号对应的右括号被遍历到为止。递归处理出括号内部的部分的哈希表之后,下一步就是看这个括号整个的系数,这个系数就是括号后面的数字;接着就是将括号和这个括号后面的系数综合一下,把这一部分的元素个数加入答案;
如果遇到了右括号,则说明递归处理完毕,直接返回答案;
如果遇到了字母,则说明当前处理到了一个元素,先将元素名截取出来(所有的元素都是一个大写字母开头,后面或者不接,或者接若干连续的小写字母)。元素后面也可能有系数,将系数截取出来,接着将这个元素和系数综合一下加入答案。
代码如下:
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
using MPSI = unordered_map<string, int>;
int n;
// dfs处理化学式,返回该化学式的各个元素及个数
MPSI dfs(string& s, int& k) {
MPSI res;
while (k < s.size() && s[k] != ')') {
if (s[k] == '(') {
k++;
// 递归处理括号内的化学式
auto mp = dfs(s, k);
// 略过右括号
k++;
// 截取出括号对应的系数
int cnt = 1, u = k;
while (u < s.size() && isdigit(s[u])) u++;
if (u > k) {
cnt = stoi(s.substr(k, u - k));
k = u;
}
// 将该括号及对应的系数综合一下加入答案
for (auto& [k, v] : mp) res[k] += v * cnt;
} else {
// 遇到字母了,则该字母是元素的第一个字母,先求元素名
int u = k + 1;
while (u < s.size() && 'a' <= s[u] && s[u] <= 'z') u++;
auto elem = s.substr(k, u - k);
// 略过元素名
k = u;
// 求一下该元素的系数
int cnt = 1;
while (u < s.size() && isdigit(s[u])) u++;
if (u > k) {
cnt = stoi(s.substr(k, u - k));
k = u;
}
res[elem] += cnt;
}
}
return res;
}
// 处理表达式
MPSI work(string s) {
MPSI res;
for (int i = 0; i < s.size(); i++) {
int j = i;
// 截取出化学式
while (j < s.size() && s[j] != '+') j++;
auto item = s.substr(i, j - i);
i = j;
// 求一下化学式开头的系数
int cnt = 1, k = 0;
while (k < item.size() && isdigit(item[k])) k++;
if (k) cnt = stoi(item.substr(0, k));
// 递归求一下化学式的元素及个数
auto mp = dfs(item, k);
for (auto& [k, v] : mp) res[k] += v * cnt;
}
return res;
}
int main() {
scanf("%d", &n);
while (n--) {
string s;
cin >> s;
int k = s.find('=');
work(s.substr(0, k)) == work(s.substr(k + 1)) ? puts("Y") : puts("N");
}
}
时空复杂度 O ( n ) O(n) O(n)。