题目描述
在遥远星球 Dreisamwaiste\texttt{Dreisamwaiste}Dreisamwaiste 沙漠的考古挖掘中,发现了一些写有神秘符号的纸张。科学家认为这些符号可能是等式的组成部分,但纸张上只保留了数字、括号和等号,而运算符(+、-、*) 已经随时间褪色消失。
已知 Dreisamwaiste\texttt{Dreisamwaiste}Dreisamwaiste 居民只使用加法、减法和乘法三种运算,并且没有运算优先级规则——他们严格按照从左到右的顺序计算表达式。例如,对于表达式 3+3∗53 + 3 * 53+3∗5 ,他们先计算 3+3=63 + 3 = 63+3=6 ,再计算 6∗5=306 * 5 = 306∗5=30 ,而不是 181818 。括号的作用是改变计算顺序,括号内的表达式先计算。
任务:对于给定的等式(不含运算符),判断是否存在一种方式插入 +++ 、 −-− 、 ∗*∗ 运算符,使等式成立(按照从左到右、括号优先的规则计算)。
输入格式
- 每行一个等式,左侧是一个正整数(小于 2302^{30}230 ),接着是等号 === ,右侧由最多 121212 个正整数组成(数字乘积小于 2302^{30}230 )
- 右侧可能包含括号,括号包围一个或多个数字
- 每行不超过 808080 个字符,两个数字之间至少有一个空格或括号
- 以单独一行 000 结束输入
输出格式
- 对于每个等式,输出 Equation #n:\texttt{Equation \#n:}Equation #n: ( nnn 为等式编号)
- 如果存在解,输出插入运算符后的等式(不包含空格)
- 如果无解,输出 Impossible.\texttt{Impossible.}Impossible.
- 每个测试用例后输出一个空行
题目分析
核心难点
- 无优先级计算规则:必须严格从左到右计算,括号内优先
- 运算符位置不确定:需要在数字之间插入运算符,但某些位置可能不需要(如括号直接相邻时)
- 括号处理:括号可以嵌套,改变计算顺序
- 搜索空间控制:最多 121212 个数字,数字间最多 111111 个运算符位置,每个位置有 333 种选择,理论上最多 311=1771473^{11} = 177147311=177147 种组合,加上括号影响,实际需要有效搜索
解题思路
1. 表达式解析与标准化
首先需要将输入的表达式解析为可处理的形式。关键观察:
- 数字之间如果有空格分隔,则必须插入运算符
- 括号前后可能需要运算符,取决于上下文:
- 数字后接左括号:需要运算符,如 7(5 3)7(5\ 3)7(5 3) 变为 7+(5−3)7+(5-3)7+(5−3)
- 右括号后接数字:需要运算符,如 (5 3)2(5\ 3)2(5 3)2 变为 (5−3)∗2(5-3)*2(5−3)∗2
- 右括号后接左括号:需要运算符,如 (1 2)(3 4)(1\ 2)(3\ 4)(1 2)(3 4) 变为 (1+2)∗(3+4)(1+2)*(3+4)(1+2)∗(3+4)
- 数字后接数字:需要运算符,如 5 35\ 35 3 变为 5+35+35+3
我们将表达式标准化为以下形式:
- 保留所有数字和括号
- 在需要插入运算符的位置用特殊字符(如 #\## )标记
例如: 18 = 7 (5 3) 2\texttt{18 = 7 (5 3) 2}18 = 7 (5 3) 2 标准化为 7#(5#3)#2\texttt{7\#(5\#3)\#2}7#(5#3)#2
2. 从左到右计算器实现
需要实现一个递归计算器,遵循以下规则:
- 遇到括号时,递归计算括号内的值
- 严格从左到右应用运算符,无优先级
- 支持加法、减法、乘法
实现方法:使用递归函数,维护当前结果、上一个运算符和当前数字,按字符处理表达式。
3. 搜索策略
使用深度优先搜索( DFS\texttt{DFS}DFS ):
- 找到所有需要插入运算符的位置( #\## 标记的位置)
- 对每个位置依次尝试 +++ 、 −-− 、 ∗*∗ 三种运算符
- 每次尝试后计算表达式值,与目标值比较
- 找到第一个解后立即返回(题目不要求所有解)
4. 优化与剪枝
- 提前终止:一旦找到解,立即停止搜索
- 递归深度有限:最多 121212 个数字,搜索深度可控
- 使用 long long\texttt{long\ long}long long 存储:防止整数溢出
算法步骤
- 读取输入:解析左侧值和右侧表达式
- 表达式标准化:
- 将右侧表达式拆分为标记( tokens\texttt{tokens}tokens )
- 根据标记间关系确定是否需要运算符
- 用 #\## 标记运算符位置,构建标准化字符串
- 收集运算符位置:记录所有 #\## 的位置
- 深度优先搜索:
- 递归地在每个 #\## 位置尝试三种运算符
- 每次尝试后计算表达式值
- 与目标值比较,找到则输出并返回
- 输出结果:按格式输出解或 Impossible.\texttt{Impossible.}Impossible.
复杂度分析
- 时间复杂度:最坏情况下 O(3m⋅n)O(3^m \cdot n)O(3m⋅n) ,其中 mmm 是运算符位置数(最多 111111 ), nnn 是表达式长度。实际通过提前终止和有限深度,性能可接受。
- 空间复杂度: O(n)O(n)O(n) ,主要用于存储表达式和递归栈。
代码实现
// Dreisam Equations
// UVa ID: 708
// Verdict: Accepted
// Submission Date: 2025-12-04
// UVa Run Time: 0.030s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
bool found;
long long leftValue;
string s;
vector<int> idxer;
// 计算表达式的值(从左到右,括号优先)
pair<long long, int> evaluate(int pos) {
long long result = 0;
char lastOp = '+'; // 第一个数字视为加法
long long currentNum = 0;
bool hasNum = false;
while (pos < s.size() && s[pos] != ')') {
char c = s[pos];
if (isdigit(c)) {
// 解析数字
currentNum = currentNum * 10 + (c - '0');
hasNum = true;
pos++;
} else if (c == '(') {
// 递归计算括号内的值
auto [subResult, newPos] = evaluate(pos + 1);
currentNum = subResult;
hasNum = true;
pos = newPos + 1; // 跳过')'
} else if (c == '+' || c == '-' || c == '*') {
// 遇到运算符,应用上一个运算符到结果
if (hasNum) {
if (lastOp == '+') result += currentNum;
else if (lastOp == '-') result -= currentNum;
else result *= currentNum;
currentNum = 0;
hasNum = false;
}
lastOp = c;
pos++;
}
}
// 处理最后一个数字
if (hasNum) {
if (lastOp == '+') result += currentNum;
else if (lastOp == '-') result -= currentNum;
else result *= currentNum;
}
return {result, pos};
}
void dfs(int idx) {
if (found) return;
if (idx == idxer.size()) {
auto [v, _] = evaluate(0);
if (v == leftValue) {
cout << leftValue << '=' << s << '\n';
found = true;
}
return;
}
for (int i = 0; i < 3; i++) {
s[idxer[idx]] = "+-*"[i];
dfs(idx + 1);
if (found) return;
}
}
int main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
string line;
int cs = 1;
while (getline(cin, line)) {
if (line == "0") break;
istringstream iss(line);
// 读入左侧值和等号
char equal;
long long v;
iss >> leftValue >> equal;
// 解析右侧表达式
string token;
vector<string> terms;
while(iss >> token) terms.push_back(token);
// 将表达式解析为紧凑形式,运算符位置使用 # 代替
s.clear();
s += terms.front();
for (int i = 1; i < terms.size(); i++) {
if (s.back() != '(' && terms[i].front() != ')') s += "#";
s += terms[i];
}
// 进一步检查是否还有遗漏,因为输入可能非常“恶心”
string normalized;
normalized += s.front();
for (int i = 1; i < s.size(); i++) {
if (normalized.back() == ')' && s[i] == '(') normalized += "#";
if (isdigit(normalized.back()) && s[i] == '(') normalized += '#';
if (normalized.back() == ')' && isdigit(s[i])) normalized += '#';
normalized += s[i];
}
s = normalized;
cout << "Equation #" << cs << ":\n";
found = false;
// 确定运算符位置
idxer.clear();
for (int i = 0; i < s.size(); i++)
if (s[i] == '#')
idxer.push_back(i);
// 回溯构造表达式
dfs(0);
if (!found) cout << "Impossible.\n";
cout << '\n';
cs++;
}
return 0;
}
总结
本题的关键在于理解 Dreisamwaiste\texttt{Dreisamwaiste}Dreisamwaiste 居民独特的计算规则(无优先级、从左到右),并正确处理括号和运算符插入位置。通过表达式标准化和深度优先搜索,可以高效地找到可能的解。需要注意处理数字与括号之间的隐含运算符需求,这是解题的难点之一。
代码实现中使用了递归计算器和回溯搜索,时间复杂度在可接受范围内,能够正确处理题目给定的约束条件。

被折叠的 条评论
为什么被折叠?



