[noip2000-普及] 计算器改良

GitHub跳转原题关系图返回列表

逐项扫描一元一次方程,左半边按原符号、右半边变号累加,合并成 ax+b=0 后解 x=-b/a 保留三位小数。

OJ: roj

题目 ID: 3498

难度:普及

标签:模拟字符串解析

创建: 2026-10-02 03:11

更新: 2026-10-06 12:08

题目描述

给定一个一元一次方程,只包含整数、一个小写字母(未知数)及 +、-、= 三种符号,没有括号和除号;- 既可作减号也可作负号,形如 a 的项省略了系数 1。方程合法且有唯一实数解,求这个解,输出格式为 未知数=值,值精确到小数点后三位。

例如输入 6a-5+1=2-2a,输出 a=0.750。

思路

把 = 右边逐项变号移到左边,合并同类项得 ax+b=0ax+b=0,解 x=−b/ax=-b/a。解析时每个项由「符号 + 数字串(可有可无)+ 字母(可有可无)」构成:末尾是字母就是未知数项(没有数字则系数为 1),否则是常数项;左半边按原符号累加、右半边取相反符号,注意解为 0 时消除 -0.000。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 03:11
# update_at: 2026-10-02 03:11

import re
import sys

TERM = re.compile(r'[+-]?(?:\d+[a-z]?|[a-z])')  # 一项:可选符号 + 数字(可带字母)或单个字母


def balance(side: str, direction: int) -> tuple[int, int]:
    """把半边的每一项移到方程左边:返回(未知数系数和,常数和),direction 表示这半边的正负号。"""
    coef = const = 0
    for term in TERM.findall(side):
        sign = -1 if term[0] == '-' else 1  # 项首的 + / - 就是这一项的符号
        body = term.lstrip('+-')
        if body[-1].isalpha():              # 未知数项:`a`、`-a` 省略了系数 1
            coef += sign * direction * int(body[:-1] or '1')
        else:                               # 常数项
            const += sign * direction * int(body)
    return coef, const


def solve() -> None:
    data = iter(sys.stdin.read().split())  # 整行方程不含空格,按空白切开只有一个 token
    equation = next(data)
    left, right = equation.split('=')
    coef, const = balance(left, 1)
    rcoef, rconst = balance(right, -1)  # 右半边整体移项到左边
    coef, const = coef + rcoef, const + rconst
    answer = -const / coef
    if answer == 0:
        answer = 0.0                    # 抵消 -0.0,避免输出 -0.000
    name = re.search(r'[a-z]', equation).group()  # 方程里唯一的小写字母就是未知数
    print(f'{name}={answer:.3f}')


if __name__ == '__main__':
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 12:08
 * update_at: 2026-10-06 12:08
 */

// 解一元一次方程:无括号,只有整数、一个未知数字母和 + - = 三种符号。
// 思路:把 = 右边整体移到左边(逐项变号),合并同类项得 coef*x + const = 0,
// 答案 x = -const / coef。
#include <cstdio>
#include <cstring>

typedef long long ll;

char s[1005];   // 存整个方程
ll len;         // 方程长度
ll pos;         // 当前解析到的下标
ll coef;        // 未知数系数和(移项后)
ll cst;         // 常数项和(移项后)
char name;      // 未知数字母

// 判断是否字母(未知数)
bool is_alpha(char c) {
    return c >= 'a' && c <= 'z';
}

// 解析一项:该项必带符号(用 side 修正右半边方向),累加进 coef / cst
// side:左半边为 1,右半边为 -1(相当于右边整体乘 -1 移到左边)
void parse_term(ll side) {
    ll sign = 1;        // 这一项的符号
    if (s[pos] == '-') {
        sign = -1;
        pos++;
    } else if (s[pos] == '+') {
        pos++;
    }
    // 项体:要么是数字串(后面可跟字母),要么是单个字母(省略系数 1)
    ll num = 0;
    bool has_digit = false;
    while (pos < len && s[pos] >= '0' && s[pos] <= '9') {
        num = num * 10 + (s[pos] - '0');
        has_digit = true;
        pos++;
    }
    if (pos < len && is_alpha(s[pos])) { // 未知数项:6a / a / -a
        if (!has_digit) num = 1;         // 省略了系数 1
        coef += side * sign * num;
        if (name == 0) name = s[pos];    // 记下未知数字母
        pos++;
    } else {                             // 常数项
        cst += side * sign * num;
    }
}

int main() {
    scanf("%s", s);
    len = strlen(s);
    pos = 0;
    coef = 0;
    cst = 0;
    name = 0;

    // 左半边:遇到 = 停
    while (pos < len && s[pos] != '=') parse_term(1);
    pos++; // 跳过 =
    // 右半边:side = -1 表示移项到左边要变号
    while (pos < len) parse_term(-1);

    // 此时方程化为 coef * x + cst = 0,x = -cst / coef(ll 除法转 double 隐式完成)
    double ans = -1.0 * cst / coef;
    if (ans == 0.0) ans = 0.0; // 消除 -0.000 的情况
    printf("%c=%.3f\n", name, ans);
    return 0;
}