题目传送门(内部题70)


输入格式

第一行一个正整数$n$,表示炼金术士已知的热化学方程式数量。
接下来$n$行,每行一个炼金术士已知的热化学方程式。
最后一行一个炼金术士想要求解的热化学方程式,末尾记为$H=?$。
每个热化学方程式都是规范的,格式如下:
$a\ W\ +\ b\ X\ +\ ...\ =\ c\ Y\ +\ d\ Z\ ...\ H=\ h$
表示$a$单位的$W$、$b$单位的$X$与......反应生成了$c$单位的$Y$、$d$单位的$Z$和......,吸收$h$/放出$-h$单位热量。
$W\ X\ Y\ Z$为化学物质名称,是一个长度不超过$5$的合法标识符(即首字符是大小写字母,其它字符都是大小写字母或数字)。$a\ b\ c\ d$为各化学物质的系数,是一个小数点后不超过$1$位的$\leqslant 10^2$的正实数。
$h$为反应焓变,是一个小数点后不超过$1$位的、绝对值$\leqslant 10^4$的实数。
$+\ =\ H=$部分均为字符串。$=$左边的物质都是反应物,右边的都是生成物。
任何相邻两个元素间都用一个空格隔开,行末无多余空格。
炼金术士的记忆很好,所以不会存在自相矛盾的情况,他回忆起的方程式也一定能够解出问题的答案。


输出格式

输出一行一个实数,表示解出的热化学方程式的焓变,四舍五入保留$1$位小数。
对于每个测试点,您的输出必须与标准答案完全一致才能得分。


样例

样例输入:

2
1 C + 1 O2 = 1 CO2 H= -393.5
1 CO + 0.5 O2 = 1 CO2 H= -283.0
2 C + 1 O2 = 2 CO H= ?

样例输出:

-221.0


数据范围与提示

数据范围:

设$m$为所有方程式中出现过的化学物质的数量。
对于$20\%$的数据,$n=1,m=2$;
对于$40\%$的数据,$n\leqslant 4,m\leqslant 10$;
对于$70\%$的数据,已知方程式没有重复信息(即不存在任意的若干个已知方程式使得它们经过变换后等于另一个已知方程式);
对于$100\%$的数据,$n,m\leqslant 200$。

下面是可能用到的化学知识,学过高中化学选修$4$第一章的同学们可以跳过了。

热化学方程式:

与一般化学方程式的区别:
  $1.$一般化学方程式的系数必须是最简整数比关系,而热化学方程式没有要求。
  $2.$热化学方程式的末尾有一个位置写明了反应的焓变(热量变化),形如$\Delta H=h$。$h$为正数表示反应吸收了热量,为负数表示放出了$-h$单位的热量。更通俗地来说,设想每种化学物质的单位内能是一定的,那么热化学方程$aW+bX+...=cY+dZ...\Delta H=h$可以这样解释:$a$单位的$W$与$b$单位的$X...$的能量之和,比$c$单位的$Y$与$d$单位的$Z...$的能量之和少$h/$多$-h$。
比如$H_2+\frac{1}{2}O_2=H_2O\ \Delta H=-h$
表示一个单位氢气与半个单位氧气生成一个单位水,放出$h$单位热量。
或者说一个单位氢气的内能加上半个单位的氧气的内能,比一个单位水的内能多$h$。
因为系数没有要求,所以上式等价于$2H_2+O_2=2H_2O\ \Delta H=-h$
因为广义下任何反应都是可逆的,于是上式还等价于$H_2O=H_2+\frac{1}{2}O_2\ \Delta H=h$

加减消元法:

盖斯定律:若一反应为二个反应式的代数和时,其反应热为此二反应热的代数和。也可表达为在条件不变的情况下,化学反应的热效应只与起始和终了状态有关,与变化途径无关。
也就是说,我们可以根据已知的热化学方程式,做上面提到的两种等价变换,再把若干方程式按照等式的法则做加减法,可以得到新的热化学方程式。下面举两个例子:
例$1$
$\begin{array}{ll} C+O_2=CO_2\ & \Delta H=h_1——① \\ CO+\frac{1}{2}O_2=CO_2\ & \Delta H=h_2——②\end{array}$
求$2C+O_2=2CO$的焓变。
解:②反向变换得
$CO_2=CO+\frac{1}{2}O_2\ \Delta H=-h_2——③$
$①+③$,左右两边消去$CO_2$得
$C+\frac{1}{2}O_2=CO\ \Delta H=h_1-h_2$
最后两边同时$\times 2$得
$2C+O_2=2CO\ \Delta H=2(h_1-h_2)$
例$2$
已知
$\begin{array}{ll} H_2+\frac{1}{2}O2=H_2O\ & \Delta H=h_1——① \\ CH_4+2O_2=CO_2+2H^2O\ & \Delta H=h_2——② \\ C_2H_4+3O_2=2CO_2+2H_2O\ & \Delta H=h_3——③ \\ C_2H_6+\frac{7}{2}O_2=2CO_2+3H_2O\ & \Delta H=h_4——④\end{array}$
求$C_2H_4+H_2=C_2H_6$的焓变。
解:$①+③+$(反向$④$)得
$C_2H_4+H_2=C_2H_6\ \Delta h_1+h_3-h_4$
可以发现题目中的$②$为多余条件。


题解

先明确一下题目中所说的$m$,其实就是不同物质数,以下也用此表示。

这道题的两个难点分别是输入和高斯消元。

先来讲我的读入方式,发现都是一个小数+一个物质,于是可以用先读小数再读$string$就好了,用$map$记录每种元素有没有出现过即可。

再将更复杂的高斯消元,发现方程数小于未知数的个数,但是我们没有必要求出这个未知数的具体大小。

我的方法好像不是正解,就是将行改为$n+1$,列改为$m+1$消元即可,最后答案就是$Map[n+1][m+1]$。

出题人说没有卡精度,但是我被卡精度了……

调了好长时间才过……

时间复杂度:$\Theta(n^3)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec{double a;string s;}e[210][210];
map<string,int> mp;
int n,m;
string str;
double Map[210][210],del;
double ans;
double gauss()
{
for(int i=1;i<=n;i++)
{
int flag=i;
for(int j=i+1;j<=n;j++)
if(fabs(Map[j][i])>fabs(Map[flag][i]))flag=j;
for(int j=1;j<=m+1;j++)
swap(Map[i][j],Map[flag][j]);
if(fabs(Map[i][i])<1e-8)continue;
double b=Map[i][i];
for(int j=i;j<=m+1;j++)Map[i][j]/=b;
for(int j=1;j<=n+1;j++)
{
if(j==i) continue;
double b=Map[j][i];
for(int k=i;k<=m+1;k++)
Map[j][k]-=Map[i][k]*b;
}
}
if(fabs(Map[n+1][m+1])<1e-8)return 0;
return -Map[n+1][m+1];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n+1;i++)
{
int res=0,now=1;
while(1)
{
res++;
cin>>e[i][res].a>>e[i][res].s;
if(!mp[e[i][res].s])mp[e[i][res].s]=++m;
e[i][res].a*=now;
string str;cin>>str;
if(str[0]=='=')now=-1;
if(str[0]=='H')
{
if(i!=n+1)cin>>e[i][++res].a;
break;
}
}
}
for(int i=1;i<=n+1;i++)
{
int res=0;
while(!e[i][++res].s.empty())
Map[i][mp[e[i][res].s]]=e[i][res].a;
Map[i][m+1]=e[i][res].a;
}
printf("%.1lf",gauss());
return 0;
}

rp++

05-11 18:14