UVa 12803 Arithmetic Expressions

题目分析

本题要求对给定的算术表达式进行求值,并按照特定格式输出结果。这是一个典型的表达式求值问题,但需要特别注意输入格式、精度处理和输出格式的要求。

输入格式分析

  • 第一行包含一个自然数 NNN,表示需要求值的表达式数量
  • 接下来的 NNN 行,每行包含一个算术表达式
  • 表达式采用完全括号化的形式,确保运算优先级明确
  • 所有符号(括号、运算符、数字)之间用空格分隔
  • 数字固定为两位小数格式
  • 支持的运算符包括:+\texttt{+}+、-\texttt{-}-、*\texttt{*}*、/\texttt{/}/

输出要求分析

  • 每个表达式输出一行,包含求值结果
  • 结果必须保留两位小数
  • 需要进行四舍五入处理
  • 特别注意负零情况的处理:−0.00499999-0.00499999−0.00499999 要输出为 −0.00-0.00−0.00
  • 结果绝对值不超过 200000020000002000000
  • 保证不会出现除零错误

解题思路

111. 词法分析(Tokenization\texttt{Tokenization}Tokenization)

由于输入中所有符号都用空格分隔,理论上可以直接按空格分割。但实际测试发现部分输入可能存在空格缺失的情况,特别是负号与数字之间。因此需要实现一个健壮的词法分析器,能够正确识别:

  • 数字(包括负数)
  • 运算符
  • 括号

关键难点在于区分一元负号(表示负数)和二元减号(表示减法运算)。判断规则为:

  • 如果负号出现在表达式开头、左括号后或其他运算符后,则为负号
  • 否则为减号运算符
222. 语法分析(Parsing\texttt{Parsing}Parsing)

表达式语法采用递归定义:

复制代码
Expression → Real | '(' Expression Op Expression ')'
Op → '+' | '-' | '*' | '/'
Real → [-]d.dd(两位小数)

由于输入是完全括号化的,可以使用递归下降分析法,算法流程为:

  • 如果当前 token\texttt{token}token 是 (\texttt{(}(,则递归解析左表达式、运算符、右表达式
  • 否则,当前 token\texttt{token}token 是数字,直接返回其数值
333. 精度处理

题目要求不能使用浮点数表示,但可以在计算过程中使用浮点数,最终输出时进行格式化和四舍五入。四舍五入规则为:

  • 0.0050.0050.005 舍入为 0.010.010.01
  • 0.004999990.004999990.00499999 舍入为 0.000.000.00
  • −0.005-0.005−0.005 舍入为 −0.01-0.01−0.01
  • −0.00499999-0.00499999−0.00499999 舍入为 −0.00-0.00−0.00
444. 算法复杂度
  • 时间复杂度:O(L)O(L)O(L),其中 LLL 是表达式长度
  • 空间复杂度:O(L)O(L)O(L),用于存储 tokens\texttt{tokens}tokens 和递归调用栈

代码实现

cpp 复制代码
// Arithmetic Expressions
// UVa ID: 12803
// Verdict: Accepted
// Submission Date: 2025-10-
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;
// 词法分析函数:将输入字符串分解为token序列
vector<string> tokenize(const string& line) {
    vector<string> tokens;
    string current;
    for (size_t i = 0; i < line.length(); i++) {
        char c = line[i];
        if (isspace(c)) {
            if (!current.empty()) {
                tokens.push_back(current);
                current.clear();
            }
        } else if (c == '(' || c == ')' || c == '+' || c == '*' || c == '/') {
            if (!current.empty()) {
                tokens.push_back(current);
                current.clear();
            }
            tokens.push_back(string(1, c));
        } else if (c == '-') {
            // 区分负号和减号:负号出现在表达式开头、左括号后或运算符后
            if (current.empty() && (tokens.empty() || tokens.back() == "(" || 
                tokens.back() == "+" || tokens.back() == "-" || tokens.back() == "*" || tokens.back() == "/")) {
                current += c;  // 负号,作为数字的一部分
            } else {
                if (!current.empty()) {
                    tokens.push_back(current);
                    current.clear();
                }
                tokens.push_back(string(1, c));  // 减号运算符
            }
        } else {
            current += c;  // 数字部分
        }
    }
    if (!current.empty()) {
        tokens.push_back(current);
    }
    return tokens;
}
// 字符串转浮点数
double str_to_double(const string& s) {
    double value;
    sscanf(s.c_str(), "%lf", &value);
    return value;
}
// 浮点数转字符串,处理四舍五入和负零情况
string double_to_str(double value) {
    double rounded = round(value * 100) / 100;  // 四舍五入到两位小数
    // 处理负零情况:原始值为负且舍入后接近零
    if (fabs(rounded) < 1e-10 && value < 0) {
        return "-0.00";
    }
    if (fabs(rounded) < 1e-10) {
        return "0.00";
    }
    char buffer[20];
    snprintf(buffer, sizeof(buffer), "%.2lf", rounded);
    return buffer;
}
// 应用运算符进行计算
double apply_op(double left, double right, const string& op) {
    if (op == "+") return left + right;
    if (op == "-") return left - right;
    if (op == "*") return left * right;
    if (op == "/") return left / right;
    return 0;
}
// 递归下降解析表达式
pair<double, int> parse(const vector<string>& tokens, int idx) {
    if (tokens[idx] == "(") {
        auto left_result = parse(tokens, idx + 1);  // 解析左表达式
        double left_val = left_result.first;
        int pos = left_result.second;
        string op = tokens[pos];  // 获取运算符
        pos++;
        auto right_result = parse(tokens, pos);  // 解析右表达式
        double right_val = right_result.first;
        pos = right_result.second;
        if (pos < tokens.size() && tokens[pos] == ")") {
            pos++;  // 跳过右括号
        }
        return {apply_op(left_val, right_val, op), pos};
    } else {
        return {str_to_double(tokens[idx]), idx + 1};  // 数字直接转换
    }
}
int main() {
    int N;
    cin >> N;
    cin.ignore();  // 忽略换行符
    for (int i = 0; i < N; i++) {
        string line;
        getline(cin, line);
        vector<string> tokens = tokenize(line);  // 词法分析
        auto result = parse(tokens, 0);  // 语法分析并求值
        cout << double_to_str(result.first) << endl;  // 格式化输出
    }
    return 0;
}

关键点总结

  1. 词法分析:正确处理负号与减号的区分是解题的关键
  2. 递归下降分析:利用表达式完全括号化的特点,简化语法分析
  3. 精度处理 :使用 double\texttt{double}double 进行计算,输出时进行四舍五入
  4. 边界情况 :特别注意负零 −0.00-0.00−0.00 的输出格式要求

该解法能够正确处理各种复杂的嵌套表达式,满足题目的所有精度和格式要求。

相关推荐
CoovallyAIHub19 小时前
AI基础设施新玩家:Tinker如何重新定义LLM微调工作流?
深度学习·算法·计算机视觉
xzk2012100219 小时前
洛谷 P1438 无聊的数列 题解
c++·算法·树状数组
OKkankan19 小时前
list的使用和模拟实现
数据结构·c++·算法·list
熬了夜的程序员20 小时前
【LeetCode】74. 搜索二维矩阵
线性代数·算法·leetcode·职场和发展·矩阵·深度优先·动态规划
蓝色汪洋20 小时前
oj字符矩阵
算法
点云SLAM20 小时前
矩阵奇异值分解算法(SVD)的导数 / 灵敏度分析
人工智能·线性代数·算法·机器学习·矩阵·数据压缩·svd算法
坚持编程的菜鸟20 小时前
LeetCode每日一题——矩阵置0
c语言·算法·leetcode·矩阵
零基础的修炼20 小时前
Linux---线程封装
linux·c++·算法
chao18984421 小时前
基于MATLAB的双摆系统阻抗控制实现
算法