201912-3 化学方程式 的一种解法

这是去年12月参加CCF没能做完的一道题,当时只获得了60分。今天闲来无事把之前的代码完善了一下,在练习平台上中终于100分通过。(第一次发博客,发一道水题试试水)

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解题思路

这道题难度不大,题目描述也清晰易懂,关键在于分析清方程式的结构。我把方程式做如下分解
方程式: <表达式> < = > <表达式>
表达式:<项> < + 项>*
项:{<系数>| 空串 } <化学式>
化学式:<(化学式)>* <元素> {<角标>| 空串 }* | <(化学式)>*
元素: <大写字母><小写字母> | <大写字母>
系数、角标:<非零数字><数字>*
(星号表示有0或N个相同的结构连接在一起)

通过分析可以发现每个方程式都被等号分为左右两个表达式,只要确保等号两边表达式中元素种类和每种元素的原子数量相同即可。表达式又被加号分为多个项。不难看出每个项的结尾只能是加号、等号或者方程式末尾,我选择根据这三个分界点找出每一项,然后逐一处理。

项由系数和化学式组成,系数只会在项的开头出现,所以只需在项的开头找出数字串并记录,得到项的系数,如果没有前缀数字串则记系数为1。接下来化学式的处理是最麻烦的,因为括号的嵌套可能会在化学式中出现。所以化学式中会混杂着元素和括号,并且元素和由括号包括的基团都可以带有后缀角标。括号表达式需要向上层括号传递数据,我这里使用一种笨办法,比较费时间空间。对每一层括号都调用一次函数,遇到左括号就调用本身。当扫描到右括号时,说明这层括号表达式处理完毕,随后将数据返回给上层括号函数。抛开括号,对元素的处理则十分容易,仅仅是判断大小写字母还是数字的问题而已。

下面是我实现的代码,在测试平台100分通过,但不确定有没有特别的用例会出问题,希望大家给出指点。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
string equa;
int getelem(int i, map<string,int>& element_upper, set<string>& eles, int time){
	string ele="";
	if(equa[i]>='A' && equa[i]<='Z'&& i<equa.length()){
		ele+=equa[i];
		i++;
		if(equa[i]>='a' && equa[i]<='z'&& i<equa.length()){
			ele+=equa[i];
			i++;
		}
	}
	int num=0;
	while(equa[i]>='0' && equa[i]<='9'&& i<equa.length()){
		num=num*10+equa[i]-'0';
		i++;
	}
	if(num==0) num=1;
	//计算元素角标,默认为1 
	if(!element_upper.count(ele)) element_upper[ele]=0;
	element_upper[ele]+=time*num;
	//计算表达式对应元素原子数 
	//cout<< time << ele;
	eles.insert(ele);
	//元素表中加入元素 
	return i;
}
int bracket(int i, map<string,int>& element_upper, set<string>& eles, int time){
	map<string,int> element;
	while(equa[i]!=')' && equa[i]!='+' && equa[i]!='='&& i<equa.length()){
		if(equa[i]=='('){
			i = bracket(i+1, element, eles, time);
		}
		else i=getelem(i,element,eles,time);
	}
	int num=0;
	if(equa[i]==')'){
		i++;
	}
	while(equa[i]>='0' && equa[i]<='9'&& i<equa.length()){
		num=num*10+equa[i]-'0';
		i++;
	}
	if(num==0) num=1;
	for(map<string,int>::iterator it=element.begin(); it!=element.end();it++){
		if(!element_upper.count(it->first)){
			element_upper[it->first]=0;
		}
		element_upper[it->first]+=num*it->second;
	}
	return i;
}
void judge(){
	map<string,int> element_left;
	map<string,int> element_right;
	set<string> eles;
	cin>>equa;
	int i=0;
	for(i=0;i<equa.length();i++){
		int time=0;
		while(equa[i]>='0' && equa[i]<='9'){
			time=time*10+equa[i]-'0';
			i++;
		}
		if(time==0) time=1;
		//计算化学式系数,默认为1 
		while(equa[i]!='+' && equa[i]!='='){	
			i=bracket(i,element_left,eles,time);
		}
		if(equa[i]=='='){
			i++;
			break;
		}
	}
	for(;i<equa.length();i++){
		int time=0;
		while(equa[i]>='0' && equa[i]<='9'){
			time=time*10+equa[i]-'0';
			i++;
		}
		if(time==0) time=1;
		while(equa[i]!='+' && equa[i]!='=' && i<equa.length()){	
			i=bracket(i,element_right,eles,time);
		}
	}
	for(set<string>::iterator it=eles.begin();it!=eles.end();it++){
		if(element_left[*it]!=element_right[*it]){
			cout<<"N"<<endl;
			return;
		}
	}
	cout<<"Y"<<endl;
}
int main(){
	cin>>n;
	while(n--){
		judge();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值