化學方程式
來源: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<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<string>
#include<sstream>
#include<map>
#include<vector>
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;
}