洛谷 P1022(计算器的改良)题解

NCL 是一家专门从事计算器改良与升级的实验室,最近该实验室收到了某公司所委托的一个任务:需要在该公司某型号的计算器上加上解一元一次方程的功能。实验室将这个任务交给了一个刚进入的新手 ZL 先生。ZL 先生被主管告之,在计算器上键入的一个一元一次方程中,只包含整数、小写字母及 这三个数学符号(当然,符号 既可作减号,也可作负号)。方程中并没有括号,也没有除号,方程中的字母表示未知数。

为了很好的完成这个任务,ZL 先生研究了一些一元一次方程的实例:

你可假设对键入的方程的正确性的判断是由另一个程序员在做,或者说可认为键入的一元一次方程均为合法的,且有唯一实数解。

输入格式

一个一元一次方程。

输出格式

解方程的结果(精确至小数点后三位)。

输入输出样例

输入 #1

6a-5+1=2-2a

输出 #1

a=0.750

思路

这道题是我第一道 AC 的 普及/提高- 题,也是最后过的新手村题,历史有点久远了(可惜直到现在我还是一名蒟蒻),也不知为啥突然就想拿出来回忆一波,嗯那就写篇题解吧。

一元一次方程怎么解大家都会,这道题关键还是如何将方程化成 的一般式。也就意味着你要获得 的系数和常数项的系数。对于一个还没化简的式子,怎样判断每一项中的数字是常数项的系数呢还是 的系数,看这一项里面有没有 。那如何将同一类别的系数合并在一起呢,简单来说就是等式左边系数带符号地相加,等式右边的同理,两个值相减。或者再简单粗暴一点,干脆把等式右边的 看成减号,把 看成加号,这样就可以无脑相加了。


不妨假定变量 letter 就是方程中表示未知量的字母。怎样获取这个字母应该容易吧:

for (int i = 0; a[i]; i++) if (a[i] >= 'A' && a[i] <= 'z') letter=a[i];

而所谓的把 看成减号,把 看成加号其实就是变号啦。接下来就是将等式右边的符号全部变过来,首先我们要探测到等号,假定我们没探测到等号时变量 equal=-1,等探测到后就把等号的位置赋给 equal

for (int i = 0; a[i]; i++){
    if (a[i] >= 'A' && a[i] <= 'z') letter=a[i];//和刚才那步合在一起做。
    else if (a[i] == '=') equal=i;  
    if (equal != -1 && a[i] == '+') a[i]='-';
    else if (equal != -1 && a[i] == '-') a[i]='+';
    }

如何将数带符号地相加呢,自然是上字符串骚操作了(详见本人前面发布的题解)。但这里有几个麻烦的地方:首先,在什么地方用 sscanf 比较好呢?肯定是有加号和减号的地方,但方程开头和等号后面紧接的那一项不一定有符号啊,那就插入一个呗。如何在数组指定位置插入指定的东西呢,我也不清楚喔,或许是这样吧… 不妨设开始的时候变量 strlenA=strlen(a)

void ins(int m,char b){
    for (int i = strlenA; i >= m; i--) a[i+1]=a[i];
    a[m]=b;
    strlenA++;//插入一个字符后,字符串长度自然要更改!
}

好了,就用这个函数来插入符号吧:

if (a[0] != '-' && a[0] != '+') ins(0,'+');
if (a[equal+1] != '-' && a[equal+1] != '+') ins(equal+1,'+');//如果你在符号变换之后才执行这个,就得插入减号哦!

同样坑爹的是形如 那样系数 1,但 1 会被省略的情况。那就继续使用上面的函数吧。

for (int i = 0; a[i]; i++){
    if ((a[i] == '+' || a[i] == '-') && a[i+1] == letter) ins(i+1,'1');
}

好了终于可以求系数了,用 xsumnsum 两个变量存起来。

for (int i = 0; a[i]; i++){
    if (a[i] == '+' || a[i] == '-'){
        sscanf(a+i,"%d",&temp);
        for (int j = i+1; a[j] ; j++){
            if (a[j] < '0' || a[j] > '9'){
                if (a[j] == letter) xsum += temp;//看看系数后面紧跟着的是下一项开头的符号,还是表示未知数的字母。
                else nsum += temp;
                break;//没错到这里就要打住了,后面就是下一项要重新 sscanf。
            }
        }
    }
}

最后相除就是了,似乎不用考虑四舍五入的问题喔,如果你遇到了类似的情况不妨看看洛谷题解区。

ans=-((float)nsum/(float)xsum);
printf("%c=%.3f",letter,ans);

AC 代码

#include <stdio.h>
#include <string.h>
int equal=-1,strlenA,xsum=0,nsum=0,temp;
char a[2333333],letter;
float ans;
void ins(int m,char b){
    for (int i = strlenA; i >= m; i--) a[i+1]=a[i];
    a[m]=b;
    strlenA++;
}
int main(){
    gets(a);
    strlenA=strlen(a);
    if (a[0] != '-' && a[0] != '+') ins(0,'+');
    for (int i = 0; a[i]; i++){
        if (a[i] >= 'A' && a[i] <= 'z') letter=a[i];
        else if (a[i] == '=') equal=i;  
        if (equal != -1 && a[i] == '+') a[i]='-';
        else if (equal != -1 && a[i] == '-') a[i]='+';
    }
    if (a[equal+1] != '-' && a[equal+1] != '+') ins(equal+1,'-');
    for (int i = 0; a[i]; i++){
        if ((a[i] == '+' || a[i] == '-') && a[i+1] == letter) ins(i+1,'1');
    }
    for (int i = 0; a[i]; i++){
        if (a[i] == '+' || a[i] == '-'){
            sscanf(a+i,"%d",&temp);
            for (int j = i+1; a[j] ; j++){
                if (a[j] < '0' || a[j] > '9'){
                    if (a[j] == letter) xsum += temp;
                    else nsum += temp;
                    break;
                }
            }
        }
    }
    ans=-((float)nsum/(float)xsum);
    printf("%c=%.3f",letter,ans);
    return 0;
}