題目背景
近日來,西西艾弗島化學研究中心的研究員們向島上的初中學生開展了化學科普活動。在活動中發現,初學化學的同學們十分苦惱於正確配平化學方程式。 而還有一些同學,則提出了一些稀奇古怪的方程式,讓研究員們幫忙配平。在配平之前,研究員們需要先判斷這個方程式是否能夠配平。
一個化學方程式,也叫化學反應方程式,是用化學式表示化學反應的式子。其等號左右兩側分別列舉了化學反應的全部反應物和生成物。每種物質都用其化學式表示。一個物質的化學式,列舉了構成該物質的各元素的原子數目。例如,水的化學式是H2O,表示水分子中含有兩個氫原子和一個氧原子。化學方程式中每種物質的化學式前面都有一個係數,表示參與反應或生成的物質的相對數目比例。例如,方程式2H2+O2=2H2O表示二分子氫氣和一分子氧氣反應生成二分子水。 我們稱一個化學方程式是配平的,是指該方程式中的反應物和生成物中,各元素原子總數目相等。例如上述方程式中,左側氫原子、氧原子的總數目分別為4和2,右側氫原子、氧原子的總數目分別為4和2,因此該方程式是配平的。
問題描述
為了配平一個化學方程式,我們可以令方程式中各物質的係數為未知數,然後針對涉及的每一種元素,列出關於係數的方程,形成一個齊次線性方程組。然後求解這個方程組,得到各物質的係數。這樣,我們就把化學方程式配平的問題,轉化為了求解齊次線性方程組的問題。 如果方程組沒有非零解,那麼這個方程式是不可以配平的。反之,如果方程組有非零解,我們就可能得到一個配平的方程式。當然,最終得到的方程式仍然需要結合化學知識進行檢驗,對此我們不再進一步考慮,僅考慮非零解的存在。
例如要配平化學方程式:Al2(SO4)3+NH3⋅H2O→Al(OH)3+(NH4)2SO4Al2(SO4)3+NH3⋅H2O→Al(OH)3+(NH4)2SO4
首先假定所有物質在方程的同一側,即不考慮哪個是反應物,哪個是生成物,分別設這些物質的係數為𝑥1,𝑥2,𝑥3,𝑥4,則可以針對出現的各個元素,列出如下的方程組:
用矩陣的形式表示為:
對係數矩陣實施高斯消元,得到係數矩陣的一個行階梯形式:
由此可見,係數矩陣的秩為3。根據線性代數的知識,我們知道,齊次線性方程組𝐴𝑋=0的解空間的維數等於其未知數個數減去係數矩陣的秩rank𝐴。而要讓方程式配平,即要求方程組存在非零解,那麼就需要讓解空間的維數大於0,即係數矩陣的秩小於未知數個數。因此,我們可以透過判斷係數矩陣的秩是否小於未知數個數,來判斷方程式是否可以配平。如果可以配平,則可以透過解的符號來判斷反應物和生成物的位置。
本題中,我們將給出一些化學方程式,請你按照上述方法判斷它們是否可以配平。為了便於程式處理,我們用到的化學式,會被化簡為只包含小寫字母和數字的字串,不包含括號。其中連續的字母表示一種元素,隨後的數字表示原子個數。原子個數為1時不省略數字;一個化學式中包含的元素不重複。例如,上述方程式中的化學式可以化簡為al2s3o12
、n1h5o1
、al1o3h3
、n2h8s1o4
。
輸入格式
從標準輸入讀入資料。
輸入的第一行包含一個正整數𝑛,表示需要判斷的化學方程式的個數。
接下來的𝑛行,每行描述了一個需要被配平的化學方程式。包含空格分隔的一個正整數和全部涉及物質的化學式。其中,正整數𝑚表示方程式中的物質;隨後的𝑚個字串,依次給出方程式中的反應物的化學式和生成物的化學式。
輸出格式
輸出到標準輸出。
輸出包含𝑛行,每行包含字母Y
或N
,表示按題設方法,所給待配平化學方程式能否配平。
樣例輸入
6
2 o2 o3
3 c1o1 c1o2 o2
2 n2o4 n1o2
4 cu1 h1n1o3 cu1n2o6 h2o1
4 al2s3o12 n1h5o1 al1o3h3 n2h8s1o4
4 c1o1 c1o2 o2 h2o1
樣例輸出
Y
Y
Y
N
Y
Y
樣例解釋
輸入中給出了 5 個待配平的化學方程式,其中各方程式的配平情況為:
- 3O2=2O3
- 2CO+O2=2CO2
- N2O4=2NO2
- 因為缺少生成物NO或NO2,所以不可以配平
- Al2(SO4)3+6NH3⋅H2O=2Al(OH)3+3(NH4)2SO4
- 2CO+O2=2CO2,本方程式對應的線性方程組求解後,得到H2O的係數為0,說明其未參與反應,屬多餘的物質。在這種情況下,由於對應的線性方程組存在非零解,所以我們仍然認為這個方程式是可以配平的。
資料範圍
對於20%的資料,每個方程中物質的個數不超過2,每個方程中涉及的全部元素不超過2種;
對於60%的資料,每個方程中物質的個數不超過3,每個方程中涉及的全部元素不超過3種;
對於100%的資料,每個方程中物質的個數不超過40,每個方程中涉及的全部元素不超過40種;且有1≤𝑛≤10,且化學式中各元素的原子個數不超過50。
提示
-
對矩陣進行高斯消元的一種方法是:
- 考察矩陣的第一列上的元素:對除去第一行第一列的子矩陣重複上述操作,直至不再餘下子矩陣。
- 若全都為零,則對除去該列的子矩陣重複上述判斷;
- 若不全為零,則:
- 考察第一行第一列的元素:
- 如果其為 0,則將該行與後面的某一個第一列非 0 的行交換,使第一行第一列的元素非 0;
- 令後續所有行減去第一行的適當倍數,使得後續所有行的第一列元素為 0;
- 考察第一行第一列的元素:
- 對除去第一行第一列的子矩陣重複上述操作,直至不再餘下子矩陣。
- 考察矩陣的第一列上的元素:對除去第一行第一列的子矩陣重複上述操作,直至不再餘下子矩陣。
-
對係數矩陣高斯消元后,不全為 0 的行的數目即為係數矩陣的秩。
-
評測環境僅提供各語言的標準庫,特別地,不提供任何線性代數庫。
題解
幾句話概括題意:
提取每一種元素在各個物質中出現的次數,列成矩陣行列式,然後計算矩陣的秩,秩小於元素個數輸出Y,否則輸出N。
有幾種物質就有幾個未知數,有幾種元素就有幾個方程。
對每一種元素列一個方程,該元素在每一種物質中出現的個數就是相應未知數的係數,沒有就是0。
把所有未知數的係數(包括0)按順序提出來擺一起就是我們要處理的行列式。
輸入給的格式是元素後面跟著個數,一種元素可能由不止一個字母組成,我們先把元素和數字分開,對於每一種物質,用map儲存該物質中出現的元素及其個數
然後,遍歷每種元素,一種元素對應行列式的一行,對於每種元素,遍歷每個物質,取出之前存在map中的相應的個數,即為行列式上相應位置的值
接下來處理行列式,遍歷每一行,對於第i行,如果全為0則忽略,如果不為0,檢查第i行第i列是否為0,如果為0,則往下找一個第i列不為0的行,把兩行所有元素交換。然後,我們要令從第i+1行到最後一行的第i列上的數都變成0,由於這是由計算機來計算的,不需要我們手動計算,因此我們可以不用考慮計算結果的複雜度,直接計算每一行第i列相對於第i行第i列的倍數,將每一行減去第i行的相應倍數就好了。計算過程可能存在分數,我直接用double來儲存結果,實際上可能會有一定的精度問題。
由於我們只需要知道矩陣的秩,之後不需要其它計算,因此我們也不需要交換行的位置,處理完整個行列式後直接統計不全為0的行數即可
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <map> 5 using namespace std; 6 int n,m; 7 double a[50][50]; // 儲存行列式 8 double tmp; 9 string str,ch; 10 map<string,int> mp[50]; // 統計物質中各種元素的個數 11 map<string,bool> cnt; // 標記方程式中出現過的元素 12 bool fg; 13 int main() 14 { 15 int i,j,k,len,s,t,z; 16 double p; 17 scanf("%d",&n); 18 while (n--) 19 { 20 scanf("%d",&m); 21 for (i=1;i<50;i++) 22 mp[i].clear(); 23 cnt.clear(); 24 for (i=1;i<=m;i++) 25 { 26 cin>>str; 27 len=str.length(); 28 for (j=0;j<len;) 29 { 30 ch=""; 31 s=0; 32 // 分離元素和數字 33 for (;j<len && str[j]>='a' && str[j]<='z';j++) 34 ch=ch+str[j]; 35 for (;j<len && str[j]>='0' && str[j]<='9';j++) 36 s=s*10+str[j]-'0'; 37 mp[i][ch]=s; // 在第i個物質中元素ch的個數為s 38 cnt[ch]=1; // 標記在方程式中出現過元素ch 39 } 40 } 41 t=0; 42 for (auto &p:cnt) // 遍歷所有出現過的元素 43 { 44 t++; 45 for (i=1;i<=m;i++) // 遍歷每個物質 46 { 47 a[t][i]=mp[i][p.first]; 48 } 49 } 50 for (i=1;i<=t;i++) 51 { 52 // 檢驗全為0則忽略該行 53 for (j=1;j<=m;j++) 54 if (a[i][j]!=0) 55 break; 56 if (j>m) continue; 57 if (a[i][i]==0) 58 { // 如果第i列為0,向下找一個不為0的整行交換 59 for (j=i+1;j<=t && a[j][i]==0;j++); 60 for (k=1;k<=m;k++) 61 tmp=a[i][k], 62 a[i][k]=a[j][k], 63 a[j][k]=tmp; 64 } 65 for (j=i+1;j<=t;j++) 66 { //將後面每一行減去第i行相應倍數,使第i列變成0 67 p=a[j][i]/a[i][i]; 68 for (k=i;k<=m;k++) 69 a[j][k]=a[j][k]-a[i][k]*p; 70 } 71 } 72 z=0; 73 for (i=1;i<=t;i++) // 統計不全為0的行數,即矩陣的秩 74 { 75 for (j=1;j<=m && a[i][j]==0;j++); 76 if (j<=m) z++; 77 } 78 if (z<m) printf("Y\n"); 79 else printf("N\n"); 80 } 81 return 0; 82 }