CCF201912-3 化学方程式*

参考

https://blog.csdn.net/wingrez/article/details/85111975

https://blog.csdn.net/richenyunqi/article/details/104147851

题解一:

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

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

1、总体上判断是否配平,也就是方程式中等号左右两边的元素种类和对应的原子个数是否相同
   
    用到map<string,int> mp1,mp2 存储
    要求mp1.size()==mp2.size()且要遍历mp1,mp2

bool judge(map<string,int> &left,map<string,int> &right){
	if(left.size()!=right.size()) return false;
	for(auto it=left.begin();it!=left.end();it++){
		if(right[it->first]!=it->second) return false;
	}
	return true;
}

2、化学方程式由左右两个表达式组成,中间用一个等号三连接,如2H2+O2=2H2O;
   表达式由若干部分组成,每部分由系数和化学式构成,部分之间用加号+连接,如2H2+O2、2H2O;
    
    “=”“+”需要拆分string
    stringstream ss(str);
    getline(ss,lstr,'=');getline(ss,rstr);
    加号的拆分要做循环,每次拆分得到一个化学式。
    stringstream ss(str);
    string item;
    while(getline(ss,item,'+')){...}

3、整数由一个或多个数字构成;
    
    写个函数把整数读取的过程分离出来,这里位置pos要&
    int toNumber(string str,int &pos){
	int num=0;
	while(isdigit(str[pos])){
		num=num*10+str[pos]-'0';
		pos++;
	}
	return num;
    }

4、接下来对化学式进行处理,分解成(元素,个数)的vector
    struct Elem{ //元素 
	string name; //名称 
	int num; //个数 
	Elem(string _name, int _num): name(_name), num(_num){}
    };
    vector<Elem> arr; //存储化学式的分解序列, 如 Ba、(、(、O、H、)、(、C、O、)、)

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

    当对化学式进行处理时,操作为:
    ①    化学式首部系数   取出
        if(isdigit(item[i])) factor=toNumber(item,i);

    开始循环,
    ②    是大写字母,接着往下读一个是不是小写字母 arr.push_back(Elem(name,1));
    ③    是(                                  arr.push_back(Elem("(",0));
    ④    是 )                                 
                                arr.push_back(Elem(")",0));
				if(!isdigit(item[i+1])) item.insert(i+1,"1");
    ⑤    是数字
            如果arr前面一个是元素,更改此元素的num
            如果arr前面是),遍历一直往前找到(,期间更改所有元素num,处理完后将()改成*表示已处理。
    
    注意事项:元素后没数字没问题,因为push时写了1,但是右括号后如果没数字,要插入一个1.
            
            
		
    

 首先要清楚系数出现位置的三种情况:
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分别记录左右两边的元素个数,再进行比较。
————————————————
版权声明:本文为CSDN博主「wingrez」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wingrez/article/details/103551680

#include<bits/stdc++.h>
using namespace std;
struct Elem{ //元素 
	string name; //名称 
	int num; //个数 
	Elem(string _name, int _num): name(_name), num(_num){}
};
int toNumber(string str,int &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,'+')){
		vector<Elem> arr; //存储化学式的分解序列, 如 Ba、(、(、O、H、)、(、C、O、)、)
		int factor=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(!isdigit(item[i+1])) item.insert(i+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(name,1);
				arr.push_back(Elem(name,1));
			}
		}
		for(int i=0;i!=arr.size();++i){
			if(arr[i].name=="*") continue;
			mp[arr[i].name]+=arr[i].num*factor;
		}
	}
}
bool judge(map<string,int> &left,map<string,int> &right){
	if(left.size()!=right.size()) return false;
	for(auto 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;
}

定义了元素的结构体,用vector来存储元素和(),当()被处理后标记成*,成功规避了嵌套的难点~

还有元素后和)后不一定有系数的问题也被很好地处理了。

题解二:

利用unordered_map<string, int> ans存储整个化学方程式中出现的原子及其对应个数。
先按=将整个方程式分成两部分。左部分所有原子默认基本系数为1,右部分所有原子默认基本系数为-1。每部分最终的原子个数要乘上这个基本系数,这样处理完整个方程式中所有原子,如果配平成功所有原子对应个数应该均为0;否则有原子个数不为0 。
对于按=将分成的两部分,再按+分成多个化学式。针对每个化学式统计每种原子出现的个数。那么如何处理带()的化学式呢?我们可以采取递归处理的方法,针对遇到的每个(,找出其对应的)的位置,递归处理该()内的化学式,注意此时该()内的系数要乘上该()后紧邻的数字。找出(对应的)的方法是,设立一个变量num,初始化为1,遍历(之后的所有字符,遇到一个(就让num加1,遇到一个)让num减1,那么使num==0的)字符即为(对应的)。
————————————————
版权声明:本文为CSDN博主「日沉云起」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/richenyunqi/article/details/104147851

find_if() 同 find() 一样,为在输入迭代器所定义的范围内查找单个对象的算法,它可以在前两个参数指定的范围内查找可以使第三个参数指定的谓词返回 true 的第一个对象。谓词不能修改传给它的对象。

find_if() 会返回一个指向被找到对象的迭代器,如果没有找到对象,会返回这个 序列的结束迭代器。

可以按如下方式使用 find_if() 来查找 numbers 中第一个大于 value 的元素: 
int value {5};
auto iter1 = std::find_if(std::begin(numbers), std::end(numbers),[value](int n) { return n > value; });

if(iter1 != std::end(numbers))
    std::cout << *iter1 << " was found greater than " << value << ".\n";
find_if() 的第三个参数是一个 lambda 表达式的谓词。这个 lambda 表达式以值的方式捕获 value,并在 lambda 参数大于 value 时返回 true。这段代码会找到一个值为 46 的元素。
#include<bits/stdc++.h>
using namespace std;
int n;
string formula;
unordered_map<string,int> ans;
int computeDigit(int &first,int last){
	int i=0;
	for(;first<=last and isdigit(formula[first]); ++first)
		i=i*10+formula[first]-'0';
	return i==0?1:i;
}
void f(int first,int last,int e){
	if(first==last or (last-first==1 and islower(formula[last]))){
		ans[formula.substr(first,last-first+1)]+=e;
		return;
	}
	e*=computeDigit(first,last);
	for(int i=first,j=i+1;i<=last;i=j,++j){
		if(isupper(formula[i])){
			if(j<=last and islower(formula[j])){
				++j;
			}
			int k=j;
			f(i,k-1,e*computeDigit(j,last));//递归处理 
		}
		else if(formula[i]=='('){
			for(int num=1;num!=0;++j){
				if(formula[j]=='(') ++num;
				else if(formula[j]==')') --num;
			}
			int k=j;
			f(i+1,k-1,e*computeDigit(j,last));
		} 
	}
}
void expression(int first,int last,int e){
	for(int i=first,j=first;i<=last;i=j+1){
		j=formula.find('+',i);
		if(j==string::npos or j>last)
			j=last+1;
		f(i,j-1,e);
	}
}
int main(){
	cin>>n;
	while(n--){
		cin>>formula;
		ans.clear();
		int k=formula.find("=");
		expression(0,k-1,1);
		expression(k+1,formula.size()-1,-1);
		auto i=find_if(ans.begin(),ans.end(),[](const pair<string,int> &p) {return p.second!=0;});
		cout<<((i==ans.end())?"Y":"N")<<"\n"; 
	}
	return 0;
}

 使用了递归写法,还有一些处理和第一种都不同,学到很多了~

总结:

1、字符串的拆分

stringstream ss(s);
string item;
while(getline(ss, item, ','))
{
    elements.push_back(item);
}
int k=formula.find("=");
expression(0,k-1,1);
expression(k+1,formula.size()-1,-1);

void expression(int first,int last,int e){
	for(int i=first,j=first;i<=last;i=j+1){
		j=formula.find('+',i);
		if(j==string::npos or j>last)
			j=last+1;
		f(i,j-1,e);
	}
}

2、对比左右两边相同与否的方法(相等or为0)

bool judge(map<string,int> &left,map<string,int> &right){
	if(left.size()!=right.size()) return false;
	for(auto it=left.begin();it!=left.end();it++){
		if(right[it->first]!=it->second) return false;
	}
	return true;
}
auto i=find_if(ans.begin(),ans.end(),[](const pair<string,int> &p) {return p.second!=0;});
cout<<((i==ans.end())?"Y":"N")<<"\n"; 

3、处理系数为1时不出现问题(逻辑上处理or默认取0),注意getnum时统一都是&pos

逻辑上判断:(这里的系数值得是后缀)
    元素后面没有系数   直接写1
    元素后面有系数     系数处理
    )后面没系数       在后面插入1
    )后面有系数       系数处理
将没有系数情况->有系数情况。
int computeDigit(int &first,int last){
	int i=0;
	for(;first<=last and isdigit(formula[first]); ++first)
		i=i*10+formula[first]-'0';
	return i==0?1:i;
}

4、处理嵌套

对于系数的第2种情况,此时数组arr的最后一个元素就是元素名称,系数作用于它即可;
对于系数的第3种情况,从数组尾部逆序遍历,直到遇到左括号,将系数作用于这个范围中的元素,同时要将这一对匹配括号从数组中删除。
我们可以采取递归处理的方法,针对遇到的每个(,找出其对应的)的位置,递归处理该()内的化学式,注意此时该
()内的系数要乘上该()后紧邻的数字。找出(对应的)的方法是,设立一个变量num,初始化为1,遍历(之后的所有
字符,遇到一个(就让num加1,遇到一个)让num减1,那么使num==0的)字符即为(对应的)。
递归处理

void f(int first,int last,int e){
        //处理单个元素,递归终点
	if(first==last or (last-first==1 and islower(formula[last]))){
		ans[formula.substr(first,last-first+1)]+=e;
		return;
	}
        //顺序处理
	e*=computeDigit(first,last);//基本系数
	for(int i=first,j=i+1;i<=last;i=j,++j){//每次j=i+1
                //这种情况下一次直接到递归终点
		if(isupper(formula[i])){
			if(j<=last and islower(formula[j])){
				++j;
			}
			int k=j;
			f(i,k-1,e*computeDigit(j,last));//递归处理
		}
                //左括号
		else if(formula[i]=='('){
                        //找到对应的右括号,递归处理
			for(int num=1;num!=0;++j){
				if(formula[j]=='(') ++num;
				else if(formula[j]==')') --num;
			}
			int k=j;
			f(i+1,k-1,e*computeDigit(j,last));//递归处理
		} 
                //注:所有的数字都在e*computeDigit中处理到了
	}
}
发布了145 篇原创文章 · 获赞 0 · 访问量 1252

猜你喜欢

转载自blog.csdn.net/qq_42671442/article/details/104735767