CCF计算机软件能力认证试题练习:201912-3 化学方程式

化学方程式

来源:CCF

标签:

参考资料:

相似题目:

题目

化学方程式,也称为化学反应方程式,是用化学式表示化学反应的式子。给出一组化学方程式,请你编写程序判断每个方程式是否配平(也就是方程式中等号左右两边的元素种类和对应的原子个数是否相同)。
本题给出的化学方程式由大小写字母、数字和符号(包括等号=、加号+、左圆括号和右圆括号)组成,不会出现其他字符(包括空白字符,如空格、制表符等),化学方程式的格式与化学课本中的形式基本相同(化学式中表示元素原子个数的下标用正常文本,如H2O写成H2O),用自然语言描述如下:

  • 化学方程式由左右两个表达式组成,中间用一个等号三连接,如2H2+O2=2H2O;
  • 表达式由若干部分组成,每部分由系数和化学式构成,部分之间用加号+连接,如2H2+O2、2H2O;
  • 系数是整数或空串,如为空串表示系数为1;
  • 整数由一个或多个数字构成;
  • 化学式由若干部分组成,每部分由项和系数构成,部分之间直接连接,如H2O、CO2、Ca(OH)2、Ba3(PO4)2;
  • 项是元素或用左右圆括号括起来的化学式,如H、Ca、(OH)、(P04);
  • 元素可以是一个大写字母,也可以是一个大写字母跟着一个小写字母,如H、O、Ca。

输入

从标准输入读入数据。
输入的第一行包含一个正整数n,表示输入的化学方程式个数。
接下来n行,每行是一个符合定义的化学方程式。

输出

输出到标准输出。
输出共n行,每行是一个大写字母Y或N,回答输入中相应的化学方程式是否配平。

输入样例1

11
H2+O2=H2O
2H2+O2=2H2O
H2+Cl2=2NaCl
H2+Cl2=2HCl
CH4+2O2=CO2+2H2O
CaCl2+2AgNO3=Ca(NO3)2+2AgCl
3Ba(OH)2+2H3PO4=6H2O+Ba3(PO4)2
3Ba(OH)2+2H3PO4=Ba3(PO4)2+6H2O
4Zn+10HNO3=4Zn(NO3)2+NH4NO3+3H2O
4Au+8NaCN+2H2O+O2=4Na(Au(CN)2)+4NaOH
Cu+As=Cs+Au

输出样例1

N
Y
N
Y
Y
Y
Y
Y
Y
Y
N

提示

1<=n<=100
输入的化学方程式都是符合题目中给出的定义的,且长度不超过1000
系数不会有前导零,也不会有为零的系数化学方程式的任何一边,其中任何一种元素的原子总个数都不超过10^9
在这里插入图片描述

解题思路

首先要清楚系数出现位置的三种情况:
1、整个化学式的首部
2、元素的右部
3、右括号的右部

如32Ba((OH)2(CO3)2)3(暂不考虑化学式的合法性)
我们从系数入手,在第一种情况下,该系数作用于化学式中的所有元素;在第二种情况下,该系数作用于紧接着的左边的元素;在第三种情况下,该系数作用于紧接着的左边的匹配括号里的所有元素,请通过上例理解。
为此,我们考虑使用一个数组将化学式的各部分存储起来arr,实现逻辑如下:
1、顺序遍历化学式
2、计算系数的第1种情况,也就是整个化学式的系数factor,继续遍历。
3、遇到左或右括号时,将左或右括号加入到arr中;遇到大写字母时,获取元素名称,将元素名称加入到arr中;遇到数字时,不存到arr中,根据系数的第2、3种情况相应处理(第1种情况已经在第二步处理完成)。
4、对于系数的第2种情况,此时数组arr的最后一个元素就是元素名称,系数作用于它即可;对于系数的第3种情况,从数组尾部逆序遍历,直到遇到左括号,将系数作用于这个范围中的元素,同时要将这一对匹配括号从数组中删除。
至此处理化学式的过程结束。
对于整个化学方程式,将其从等号两边分开处理。使用两个map分别记录左右两边的元素个数,再进行比较。

参考代码

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

struct Elem{ //元素 
	string name; //名称 
	int num; //个数 
	Elem(string _name, int _num): name(_name), num(_num){}
};

int toNumber(string str, int &pos){ //从str的pos位置开始,得到一个数字 
	int num=0;
	while(isdigit(str[pos])){
		num=num*10+str[pos]-'0';
		pos++;
	}
	return num; 
} 

void calc(string &str, map<string, int> &mp){
	stringstream ss(str);
	string item;
	
	while(getline(ss, item, '+')){ //获取每一个化学式,如 32Ba((OH)2(CO3)2)3 
	 
		vector<Elem> arr; //存储化学式的分解序列, 如 Ba、(、(、O、H、)、(、C、O、)、) 
		int factor=1; //整个化学式的系数,默认为1 
		int i=0;
		
		if(isdigit(item[i])) factor=toNumber(item,i); //计算化学式系数
		 
		while(i<item.size()){
			if(isdigit(item[i])){ //处理数字
				int num=toNumber(item,i);
				if(arr[arr.size()-1].name==")"){ //序列最后一个元素是右括号 
					int j=arr.size()-1;
					arr[j].name="*"; //将右括号标记为*,忽略它的存在 
					while(arr[--j].name!="("){
						arr[j].num*=num;
					}
					arr[j].name="*"; //将左括号标记为*,忽略它的存在 
				}
				else arr[arr.size()-1].num*=num; //序列最后一个元素是元素名称 
			}
			else if(item[i]=='('){ //处理左括号 
				arr.push_back(Elem("(", 0));  //括号加入到序列中
				i++;
			}
			else if(item[i]==')'){ //处理右括号
				arr.push_back(Elem(")", 0));  //括号加入到序列中
				if(i+1==item.size() || !isdigit(item[i+1])) item.insert(i+1,"1"); //考虑到右括号右边可能不出现数字,补充底数1 
				i++;
			}
			else if(isupper(item[i])){ //处理大写字母 
				//得到元素名称 
				string name="";
				name+=item[i]; //大写字目 
				i++;
				if(islower(item[i])){ //小写字母 
					name+=item[i];
					i++;
				}
				arr.push_back(Elem(name,1)); //名称加入到序列中 
			}
		}
		
		for(int i=0; i!=arr.size(); ++i){ //将“元素->个数”保存到map中 
			if(arr[i].name=="*") continue; //忽略序列中括号的存在 
			mp[arr[i].name]+=arr[i].num*factor;
		}
		
	}
	
}

bool judge(map<string, int> &left, map<string, int> &right){ //判断两个map是否相同 
	if(left.size()!=right.size()) return false;
	for(map<string, int>::iterator it=left.begin(); it!=left.end(); ++it){
		if(right[it->first]!=it->second) return false;
	}
	return true;
}

int main(){
	int n;
	scanf("%d", &n);
	for(int i=0; i<n; ++i){
		map<string, int> left, right;
		string str, lstr, rstr;
		cin>>str;
		stringstream ss(str);
		getline(ss, lstr,'='); //得到等号左边的字符串 
		getline(ss, rstr); //得到等号右边的字符串 
	
		calc(lstr, left); //计算左字符串 
		calc(rstr, right);
		
		if(judge(left, right)) cout<<"Y"<<endl;
		else cout<<"N"<<endl; 
	}
	return 0;
}

你可能感兴趣的:(【记录】算法题解)