UVa 708 Dreisam Equations

题目描述

在遥远星球 Dreisamwaiste\texttt{Dreisamwaiste}Dreisamwaiste 沙漠的考古挖掘中,发现了一些写有神秘符号的纸张。科学家认为这些符号可能是等式的组成部分,但纸张上只保留了数字、括号和等号,而运算符(+、-、*) 已经随时间褪色消失。

已知 Dreisamwaiste\texttt{Dreisamwaiste}Dreisamwaiste 居民只使用加法、减法和乘法三种运算,并且没有运算优先级规则——他们严格按照从左到右的顺序计算表达式。例如,对于表达式 3+3∗53 + 3 * 53+35 ,他们先计算 3+3=63 + 3 = 63+3=6 ,再计算 6∗5=306 * 5 = 3065=30 ,而不是 181818 。括号的作用是改变计算顺序,括号内的表达式先计算。

任务:对于给定的等式(不含运算符),判断是否存在一种方式插入 +++−-∗* 运算符,使等式成立(按照从左到右、括号优先的规则计算)。

输入格式

  • 每行一个等式,左侧是一个正整数(小于 2302^{30}230 ),接着是等号 === ,右侧由最多 121212 个正整数组成(数字乘积小于 2302^{30}230
  • 右侧可能包含括号,括号包围一个或多个数字
  • 每行不超过 808080 个字符,两个数字之间至少有一个空格或括号
  • 以单独一行 000 结束输入

输出格式

  • 对于每个等式,输出 Equation #n:\texttt{Equation \#n:}Equation #n:nnn 为等式编号)
  • 如果存在解,输出插入运算符后的等式(不包含空格)
  • 如果无解,输出 Impossible.\texttt{Impossible.}Impossible.
  • 每个测试用例后输出一个空行

题目分析

核心难点

  1. 无优先级计算规则:必须严格从左到右计算,括号内优先
  2. 运算符位置不确定:需要在数字之间插入运算符,但某些位置可能不需要(如括号直接相邻时)
  3. 括号处理:括号可以嵌套,改变计算顺序
  4. 搜索空间控制:最多 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+(53)
    • 右括号后接数字:需要运算符,如 (5 3)2(5\ 3)2(5 3)2 变为 (5−3)∗2(5-3)*2(53)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 ):

  1. 找到所有需要插入运算符的位置( #\## 标记的位置)
  2. 对每个位置依次尝试 +++−-∗* 三种运算符
  3. 每次尝试后计算表达式值,与目标值比较
  4. 找到第一个解后立即返回(题目不要求所有解)
4. 优化与剪枝
  • 提前终止:一旦找到解,立即停止搜索
  • 递归深度有限:最多 121212 个数字,搜索深度可控
  • 使用 long long\texttt{long\ long}long long 存储:防止整数溢出

算法步骤

  1. 读取输入:解析左侧值和右侧表达式
  2. 表达式标准化
    • 将右侧表达式拆分为标记( tokens\texttt{tokens}tokens
    • 根据标记间关系确定是否需要运算符
    • #\## 标记运算符位置,构建标准化字符串
  3. 收集运算符位置:记录所有 #\## 的位置
  4. 深度优先搜索
    • 递归地在每个 #\## 位置尝试三种运算符
    • 每次尝试后计算表达式值
    • 与目标值比较,找到则输出并返回
  5. 输出结果:按格式输出解或 Impossible.\texttt{Impossible.}Impossible.

复杂度分析

  • 时间复杂度:最坏情况下 O(3m⋅n)O(3^m \cdot n)O(3mn) ,其中 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 居民独特的计算规则(无优先级、从左到右),并正确处理括号和运算符插入位置。通过表达式标准化和深度优先搜索,可以高效地找到可能的解。需要注意处理数字与括号之间的隐含运算符需求,这是解题的难点之一。

代码实现中使用了递归计算器和回溯搜索,时间复杂度在可接受范围内,能够正确处理题目给定的约束条件。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值