题目大意
输入化学方程式配平,如果有多组结果,就输出系数最小的整数解。
输入格式为:每行输入一种物质。以"+?“开头,”+“代表在等式左边,该物质初始系数是”?",第二个字符串代表接下来有几个元素,然后是每个元素,每个元素后面跟它在这个物质中的个数,水就是H2O1。以"0 0"代表输入结束
解题思路
比赛时根本没有看这道题,实际上就主要考察高斯消元的应用。但是这个题目还有两个比较难搞的地方,一个是输入,另外一个是含有一个未知变元并求出一组最小整数解
首先我们解决输入:使用 g e t l i n e getline getline读入一行字符串,接着使用 s t r i n g s t r e a m stringstream stringstream分割空格得到每个字符串并记录个数,接着我们通过开头的字符串确定初始系数是正还是负,并得到系数的大小。然后就是处理每个元素了。因为元素可能有多个字母如“ A g Ag Ag”,那么我们使用 s t r i n g string string存元素,然后用 m a p map map给每个元素赋值一个 I D ID ID。接下来是重点了,如何得到方程组呢?对第一个样例,我们手推不难发现方程组中未知数个数对应物质个数,这个我们之前有记录。那么如何将方程组对号入座,总的来说是根据元素 I D ID ID确定行,根据第几个物质确定列。具体的:对于每个元素,我们都可以列一个方程组,那么每当我们处理一个元素,就根据 I D ID ID判断它是哪一个方程,接着根据后面跟的个数乘以系数, c o l col col代表这是第几个物质,我们有 a [ m p [ s t r [ i ] ] ] [ c o l ] + = ( d o u b l e ) y ; a[ mp[str[i]] ][col]+=(double)y; a[mp[str[i]]][col]+=(double)y;,注意这里要 + = += +=,因为像 C H O O H CHOOH CHOOH这样的物质, O O O和 H H H在同一物质多次出现,那么就累加到同一个方程的同一个未知数上
接下来主要算法是高斯消元,可以网上自行学习,也可以参考我的博客。但是需要注意方程组有可能存在一个通解,而且一定是最后一行(该行打印出来全为
0
0
0),因此我们直接令该通解为
1
1
1,接下来就是如何将一组浮点数通分化为最小的一组整数解,对于下列一组浮点数解:
对于每个非整数的浮点数从
k
=
2
k=2
k=2开始不断乘,
k
+
+
k++
k++,直到将该浮点数化为整数,那么返回
k
k
k,对于整数的话直接返回1,我们得到:
接着我们求这一组数的最小公倍数,再将每个浮点数乘以这个最小公倍数即可得到最小的整数解
本来我是只会高斯-约旦消元的,也就是把增广矩阵化为行最简矩阵,但是超时了,无奈我又去学习了一下正常的高斯消元(只需要矩阵变换为上三角/下三角矩阵),跑的很快,事实证明了高斯-约旦消元效率太低,以后尽量写高斯消元。
代码:
#include <string>
#include <math.h>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <tr1/unordered_map>
using namespace std;
const double esp=1e-8;
tr1::unordered_map<string,int> mp;
double a[1005][1005];
int b[1005];
string str[105];
int num,col; //num是元素个数,col是方程个数
int gcd(int n,int m){
return m==0?n:gcd(m,n%m);
}
int lcm(int n,int m){
return n/gcd(n,m)*m;
}
void getID(string s){ //ID化字符串
if(!mp.count(s)){
mp[s]=++num;
}
}
int getNumber(string s){ //字符串转换为数字
int ans=0;
for(int i=0;i<s.size();i++){
if(s[i]>'0' && s[i]<'9') ans=ans*10+s[i]-'0';
}
return ans;
}
int check(double p){ //判断一个浮点数需要最小乘以多大的整数变成整数
int q=(int)p;
if(fabs(p-q)<esp) return 1;
else{
int k=1;
double res=p;
while(fabs(res-q)>esp){
k++;
res=p*k;
q=(int)res;
}
return k;
}
}
void solve(){ //求最小整数解
for(int i=1;i<=col;i++){
b[i]=check(fabs(a[i][col+1]));
}
int ans=b[1];
for(int i=2;i<=col;i++){
ans=lcm(ans,b[i]);
}
for(int i=1;i<=col;i++) a[i][col+1]*=ans;
}
/*打印矩阵
bool judge(double p){
int q=(int)p;
if(fabs(p-q)<esp) return 1;
return 0;
}
void print(){
for(int i=1;i<=col;i++){
for(int j=1;j<=col+1;j++)
if(judge(a[i][j])) printf("%.0lf ",a[i][j]);
else printf("%.1lf ",a[i][j]);
printf("\n");
}
printf("\n");
}*/
void gauss(int n) {
for(int i=1;i<n;i++){
int temp=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[temp][i])) temp=j;
if(fabs(a[temp][i])<esp) return;
if(temp!=i){
for(int j=i;j<=n+1;j++)
swap(a[i][j],a[temp][j]);
}
double p;
for(int j=i+1;j<=n;j++) {
p=a[j][i]/a[i][i];
for(int k=i;k<=n+1;k++)
a[j][k]-=a[i][k]*p;
}
}
if(fabs(a[n][n])<esp){ //如果有变元,那么一定是在最后一行
a[n][n]=a[n][n+1]=1;
}
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++)
a[i][n+1]-=a[i][j]*a[j][n+1];
a[i][n+1]/=a[i][i];
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
string s,buf;
num=col=0;
mp.clear();
memset(a,0,sizeof a);
while(getline(cin,s)){
stringstream ss(s);
int tot=0;
while(ss>>buf){
str[++tot]=buf;
}
if(tot==2 && str[1]=="0" && str[2]=="0") break;
col++;
char op=str[1][0];
int cnt=getNumber(str[1]);
if(op=='+'){
for(int i=3;i<=tot;i+=2){
getID(str[i]);
int y=getNumber(str[i+1]);
y*=cnt;
a[mp[str[i]]][col]+=(double)y;
}
}else{
cnt=0-cnt;
for(int i=3;i<=tot;i+=2){
getID(str[i]);
int y=getNumber(str[i+1]);
y*=cnt;
a[mp[str[i]]][col]+=(double)y;
}
}
}
gauss(col);
//print();
solve();
for(int i=1;i<=col;i++)
printf("%.0lf ",a[i][col+1]);
return 0;
}