题目分析
本题要求对给定的算术表达式进行求值,并按照特定格式输出结果。这是一个典型的表达式求值问题,但需要特别注意输入格式、精度处理和输出格式的要求。
输入格式分析
- 第一行包含一个自然数 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;
}
关键点总结
- 词法分析:正确处理负号与减号的区分是解题的关键
- 递归下降分析:利用表达式完全括号化的特点,简化语法分析
- 精度处理 :使用 double\texttt{double}double 进行计算,输出时进行四舍五入
- 边界情况 :特别注意负零 −0.00-0.00−0.00 的输出格式要求
该解法能够正确处理各种复杂的嵌套表达式,满足题目的所有精度和格式要求。