CCF-CSP认证201912-3. 化学方程式

题目链接:201912-3化学方程式

难点:

()的多重嵌套.可能出现多个嵌套,一般方法都是递归或者用堆栈,这里用了一个“pass”标记,减轻了工作量。
因为带括号的化学式出现的形式是( ( ( )num1 )num2 )num3…,按下标顺序读入的字符串遇到的第一个数字num1,然后就会处理num1所在的化学式()num1.然后再往后处理num2所在的化学式()num2,依次处理.
所以可以在遇到数字之后,根据上一个位置是不是),找到最近的(.

代码一

#include
#include
#include
#include
#include

using namespace std;

map<string, int> mp[2];
typedef pair<string, int> Elem; // 定义元素,string 存 id , num 存 个数

// 造数: 注意下标要传引用或者指针,保证下标最终指向非数字
int toNumber(string str, int &pos) {
	int flag = 0;
	while (isdigit(str[pos])) 
		flag = flag * 10 + str[pos++] - '0';
	return flag;
}

void solve(string str, int tag) {
	int coe = 1, pos = 0;		// coe: 系数, pos: 字符串下标
	vector<Elem> arr;			// 存元素的数组
	if (isdigit(str[pos])) coe = toNumber(str, pos); //算出全局系数,也就是最前面的数字

	while (pos < str.size()) {
		if (isdigit(str[pos])) {	// 遇到数字,就开始计算,除了全局系数,其他系数都存在于元素末尾
			int num = toNumber(str, pos);
			int i = arr.size() - 1;
			if (arr[i].first == ")") {	// 数字前一位是')',那么就要一个 '(' , ')' 组合内的所有元素乘倍数
				arr[i].first = "pass";	// 并修改标记,表示已经用过,避免嵌套的括号二次利用
				while (arr[--i].first != "(") arr[i].second *= num; // 找到对应的'('并修改标记
				arr[i].first = "pass";
			}
			else arr[i].second *= num; // 若数字前不是括号,则为元素本身,那么直接乘倍就好了
		}
		else if (str[pos] == '(') { //直接入数组
			arr.push_back(Elem("(", 0));
			pos++;
		}
		else if (str[pos] == ')') {
			arr.push_back(Elem(")", 0));
			if (!isdigit(str[++pos])) str.insert(pos, "1"); // 如果')'后面没有数字,那么我们就给他一个‘1’,保证所有都是')'数字结尾
		}
		else if (isupper(str[pos])) { // 遇到元素
			string name = "";
			name += str[pos];
			pos++;
			if (islower(str[pos])) { //若第二位为小写
				name += str[pos];
				pos++;
			}
			arr.push_back(Elem(name, 1)); //默认初始值都是 1
		}
	}

	for (int i = 0; i != arr.size(); i++) { 
		if (arr[i].first == "pass" ) continue; // 遇到非元素直接pass
		mp[tag][arr[i].first] += arr[i].second * coe;
	}
}

bool Judge(string str) {
	mp[0].clear();
	mp[1].clear();
	int pos = str.find('=');
	string tmp;
	string left = str.substr(0, pos);
	string right = str.substr(pos + 1);
	stringstream left_ss(left), right_ss(right);

	while (getline(left_ss, tmp, '+')) solve(tmp, 0);
	while (getline(right_ss, tmp, '+')) solve(tmp, 1);

	if (mp[0].size() != mp[1].size()) return false;

	for (map<string, int>::iterator it = mp[0].begin(); it != mp[0].end(); it++) 
		if (mp[1][it->first] != it->second) return false;

	return true;
}

int main() {
	int n;
	string str;
	cin >> n;
	while (n--) {
		cin >> str;
		if (Judge(str)) cout << "Y" << endl;
		else cout << "N" << endl;
	}
	return 0;
}

参考:代码一

代码二

思路:
利用unordered_map ans存储整个化学方程式中出现的原子及其对应个数.

先按=将整个方程式分成两部分。左部分所有原子默认基本系数为1,右部分所有原子默认基本系数为-1。每部分最终的原子个数要乘上这个基本系数,这样处理完整个方程式中所有原子,如果配平成功所有原子对应个数应该均为0;否则有原子个数不为0 。

对于按=将分成的两部分,再按+分成多个化学式。针对每个化学式统计每种原子出现的个数。

如何处理()?这里采取递归处理的方法,针对遇到的每个(,找出其对应的位置,递归处理该()的化学式,注意此时该()内的系数要乘上该()后紧邻的数字.
找出(对应的)的方法是,设立一个num,初始化为1,遍历(之后的所有字符,遇到一个(就让num加1,遇到一个)就让num减1,那么使num==0的)字符即为(对应的).
这种方法与代码一中的方法也不同,而且比栈更好用.

else if (formula[i] == '(') {  //遇到(
            for (int num = 1; num != 0; ++j) {  //找到对应的)位置存储在j中
                if (formula[j] == '(')
                    ++num;
                else if (formula[j] == ')')
                    --num;
            }
            int k = j;
            f(i + 1, k - 1, e * computeDigit(j, last));  //递归处理
        }

参考:代码二

总结:大型模拟题,细心.

你可能感兴趣的:(CCF-CSP,C++)