ICPC North Central NA Contest 2017 - A Stoichiometry(高斯消元)

题目链接


题目大意

输入化学方程式配平,如果有多组结果,就输出系数最小的整数解。

输入格式为:每行输入一种物质。以"+?“开头,”+“代表在等式左边,该物质初始系数是”?",第二个字符串代表接下来有几个元素,然后是每个元素,每个元素后面跟它在这个物质中的个数,水就是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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值