2 条题解
-
0
这是一份严格遵循 NOIP C++14 规范的标准解答代码。代码风格简洁明了,没有使用晦涩的高级特性,非常适合中学生阅读和在考场上编写。
核心设计思路
- 数据结构:
std::map<string, int>:用于存储元素周期表,实现 或接近 的快速查找。std::stack<int>:这是处理嵌套结构(如(SO4)2)的灵魂。栈不仅存储了当前的质量,利用-1作为层级分隔符,完美解决了括号递归的问题。
- 解析逻辑:
- 采用一次遍历(Single Pass)策略。
- 将解析过程模块化:区分“左括号”、“右括号”和“元素”三种情况。
C++14 标准解答代码
/** * 题目: 门捷列夫的账单 (Mendeleev's Receipt) * 语言: C++14 (NOIP Standard) * 算法: 栈 (Stack) / 字符串模拟 * * 复杂度分析: * 时间复杂度: O(L), 其中 L 是化学式字符串的长度。 * 空间复杂度: O(L + N), 用于存储栈和元素周期表。 */ #include <iostream> #include <string> #include <vector> #include <stack> #include <map> #include <cctype> // 用于 isupper, islower, isdigit using namespace std; // 元素周期表映射:符号 -> 相对原子质量 map<string, int> periodic_table; // 辅助函数:解析紧跟在某位置后的数字 // 引用传递 pos,以便主循环能跳过已读的数字 int parse_number(const string& s, int& pos) { int num = 0; bool has_num = false; // 只要是数字就一直读 while (pos < s.length() && isdigit(s[pos])) { num = num * 10 + (s[pos] - '0'); pos++; has_num = true; } // 如果后面没有数字,默认为 1 (例如 H2O 中的 O) return has_num ? num : 1; } int main() { // 1. IO 优化:信奥赛必备 ios_base::sync_with_stdio(false); cin.tie(NULL); // 2. 读入元素周期表元素的原子量 int n; if (!(cin >> n)) return 0; for (int i = 0; i < n; i++) { string sym; int w; cin >> sym >> w; periodic_table[sym] = w; } // 3. 读入化学式 string s; cin >> s; // 4. 开始解析 // 栈中存储计算过程中的质量块 // 使用 -1 作为左括号 '(' 的标记(假设质量不会为负) stack<int> stk; int i = 0; while (i < s.length()) { if (s[i] == '(') { // 遇到左括号,压入标记 stk.push(-1); i++; } else if (s[i] == ')') { // 遇到右括号,开始结算括号内的总质量 i++; // 跳过 ')' int group_mass = 0; // 不断弹出直到遇到标记 -1 while (!stk.empty() && stk.top() != -1) { group_mass += stk.top(); stk.pop(); } // 弹出标记 -1 if (!stk.empty()) stk.pop(); // 解析括号后面的倍数 (例如 (OH)2 中的 2) int multiplier = parse_number(s, i); // 将 (括号内总质量 * 倍数) 作为一个整体压回栈中 stk.push(group_mass * multiplier); } else if (isupper(s[i])) { // 遇到大写字母,说明是一个新元素的开始 string name = ""; name += s[i++]; // 检查是否有第二个小写字母 (例如 Na, Cl) if (i < s.length() && islower(s[i])) { name += s[i++]; } // 查表获取原子质量 int mass = periodic_table[name]; // 解析该原子的个数 (例如 H2 中的 2) int count = parse_number(s, i); // 将 (原子质量 * 个数) 压入栈中 stk.push(mass * count); } } // 5. 汇总栈中所有结果 // 此时栈里散落着各个原子团或原子的总质量,加起来就是分子的总质量 int total_molecular_mass = 0; while (!stk.empty()) { total_molecular_mass += stk.top(); stk.pop(); } cout << total_molecular_mass << endl; return 0; }阿西莫夫的代码注释
- 引用传递 (
int& pos):这是 C++ 的一个优雅特性。在parse_number函数中,我们不仅读取了数字,还把主循环的索引指针i向后移动了。这避免了在主循环里写乱七八糟的索引计算逻辑。 - 标记法 (
-1):在栈中混入特殊值是处理层级关系的常用技巧。因为原子质量永远是正数,所以-1是绝对安全的。 - Map 的使用:在 较小(如本题 )时,
std::map的性能绰绰有余,且代码可读性极高。如果对性能有极端要求,可以用数组模拟哈希或字典树(Trie),但在 NOIP 普及组/提高组中,map是最稳妥的选择。
- 数据结构:
-
0
阿西莫夫的解题指南
这道题是栈 (Stack) 的绝佳应用场景,类似于计算机科学中经典的“后缀表达式求值”或“带括号的四则运算”。
核心逻辑:
我们需要维护一个栈来存储当前的质量总和。
- 扫描字符串:从左到右遍历化学式。
- 遇到左括号
(:这意味着一个新的原子团开始了。我们往栈里压入一个特殊的标记(比如-1),表示“新的一层开始了”。 - 遇到元素:
- 解析出元素的名字(注意
C和Cl的区别:看下一个字符是不是小写)。 - 在哈希表(Map)中查出它的质量。
- 解析紧跟在后面的数字(如果有)。
- 计算
质量 * 数量,并将结果压入栈中。
- 解析出元素的名字(注意
- 遇到右括号
):- 这意味着当前原子团结束了。
- 关键步骤:不断从栈中弹出数字并累加,直到遇到那个特殊的标记(
-1)。这个累加和就是括号内物质的总质量。 - 弹出
-1。 - 解析右括号后面紧跟的数字(倍数)。
- 将
括号内总质量 * 倍数的结果压回栈中。
- 最后:栈里剩下的所有数字之和,就是分子的总质量。
C++ 标准解答与数据生成器
以下代码包含Solution(标准解答)和Generator(数据生成器)。它会生成
1.in~10.in及其对应的.out文件。请保存为
gen_chemistry.cpp并运行。/** * Problem: Mendeleev's Receipt (Chemical Formula Parser) * Author: Isaac Asimov (AI) * Standard: C++14 */ #include <iostream> #include <string> #include <vector> #include <stack> #include <map> #include <cctype> #include <fstream> #include <random> using namespace std; // ========================================== // Part 1: 标准解答逻辑 (The Solver) // ========================================== class Solution { private: map<string, int> periodic_table; // 辅助函数:从字符串pos位置解析一个数字,更新pos int parse_number(const string& s, int& pos) { if (pos >= s.length() || !isdigit(s[pos])) { return 1; // 默认是1 } int num = 0; while (pos < s.length() && isdigit(s[pos])) { num = num * 10 + (s[pos] - '0'); pos++; } return num; } // 辅助函数:从字符串pos位置解析元素名,更新pos string parse_element(const string& s, int& pos) { string name = ""; name += s[pos++]; // 第一个大写字母 if (pos < s.length() && islower(s[pos])) { name += s[pos++]; // 可能的第二个小写字母 } return name; } public: void solve(string in_file, string out_file) { ifstream cin(in_file); ofstream cout(out_file); if (!cin.is_open()) return; int n; cin >> n; periodic_table.clear(); for (int i = 0; i < n; i++) { string sym; int w; cin >> sym >> w; periodic_table[sym] = w; } string s; cin >> s; // 栈用于存储质量。用 -1 代表左括号这一层的边界 stack<int> stk; int i = 0; while (i < s.length()) { if (s[i] == '(') { stk.push(-1); // 压入标记 i++; } else if (s[i] == ')') { i++; // 跳过 ')' // 结算括号内的总质量 int group_mass = 0; while (!stk.empty() && stk.top() != -1) { group_mass += stk.top(); stk.pop(); } if (!stk.empty()) stk.pop(); // 弹出 -1 // 处理括号后的倍数 int multiplier = parse_number(s, i); stk.push(group_mass * multiplier); } else if (isupper(s[i])) { // 解析元素 string name = parse_element(s, i); int mass = periodic_table[name]; // 解析原子个数 int count = parse_number(s, i); stk.push(mass * count); } } // 汇总栈中所有剩余质量 int total_mass = 0; while (!stk.empty()) { // 注意:正常逻辑下最后栈里不会有 -1,除非括号不匹配 if (stk.top() != -1) total_mass += stk.top(); stk.pop(); } cout << total_mass << endl; cin.close(); cout.close(); cout << "Generated: " << out_file << endl; } }; // ========================================== // Part 2: 数据生成逻辑 (The Generator) // ========================================== mt19937 rng(2025); // 真实的元素数据,确保生成的化学式看起来像那么回事 struct Element { string name; int weight; }; vector<Element> elements = { {"H", 1}, {"C", 12}, {"N", 14}, {"O", 16}, {"Na", 23}, {"Mg", 24}, {"Al", 27}, {"S", 32}, {"Cl", 35}, {"K", 39}, {"Ca", 40}, {"Fe", 56}, {"Cu", 64}, {"Zn", 65}, {"Ag", 108} }; // 递归生成随机化学式 // depth: 嵌套深度,防止无限递归 string gen_formula(int depth) { if (depth > 2) { // 深度太深时,只返回简单的元素 int idx = rng() % elements.size(); int count = rng() % 5 + 1; return elements[idx].name + (count > 1 ? to_string(count) : ""); } string res = ""; int parts = rng() % 3 + 1; // 这一层由几部分组成 for (int p = 0; p < parts; p++) { if (rng() % 3 == 0) { // 33% 概率生成括号组 res += "(" + gen_formula(depth + 1) + ")"; int count = rng() % 4 + 2; // 括号通常有倍数 res += to_string(count); } else { // 66% 概率生成普通元素 int idx = rng() % elements.size(); res += elements[idx].name; if (rng() % 2 == 0) { // 50% 概率有下标 res += to_string(rng() % 9 + 2); } } } return res; } void generate_input(int case_id) { string filename = to_string(case_id) + ".in"; ofstream fout(filename); // 1. 输出元素表 (固定使用我们的常用表) fout << elements.size() << endl; for (auto e : elements) { fout << e.name << " " << e.weight << endl; } // 2. 生成化学式 string formula; if (case_id == 1) formula = "H2O"; else if (case_id == 2) formula = "Ca(OH)2"; else if (case_id == 3) formula = "KAl(SO4)2"; // 明矾 else if (case_id == 4) formula = "C6H12O6"; // 葡萄糖 else if (case_id == 5) formula = "Na2CO3"; // 纯碱 else if (case_id == 6) formula = "Fe(SCN)3"; // 硫氰化铁 else if (case_id == 7) formula = "Cu(NH3)4SO4"; // 铜氨络合物 else if (case_id == 8) { // 长链测试 formula = ""; for(int i=0; i<50; i++) formula += "C"; } else { // 随机复杂结构 formula = gen_formula(0); } fout << formula << endl; fout.close(); } int main() { Solution solver; cout << "--- Generating Chemistry Data ---" << endl; for (int i = 1; i <= 10; i++) { generate_input(i); string in = to_string(i) + ".in"; string out = to_string(i) + ".out"; solver.solve(in, out); } cout << "--- Done! ---" << endl; return 0; }阿西莫夫的点拨
这道题的精妙之处在于它不仅考了编程,还潜移默化地复习了化学知识。
- 数据结构的映射:
Map完美对应了化学家的“元素周期表”查询过程。 - 递归与栈:化学式中的括号结构(如
(SO4)2)是自然界中存在的递归结构。理解了这一点,学生就会明白为什么我们要学习栈——它是处理这种层级结构的通用工具,无论是解析化学式、数学公式,还是编译器的语法分析。 - 细节处理:区分
N(氮) 和Na(钠) 是一个经典的字符串处理考点(贪心匹配)。
通过这道题,学生仿佛化身为一位严谨的合成化学家,用代码称量着分子的重量。
- 1
信息
- ID
- 19236
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- (无)
- 标签
- (无)
- 递交数
- 0
- 已通过
- 0
- 上传者