题解 P1054 【等价表达式】

· · 题解

作为一名已经退役的选手,平时闲着没事情做一点题,给脑子上点油

因为已经不参加OI了,所以果断用了c++ 17... 因此代码仅供参考,但是思路是通用的:

若我们要对一个表达式求值,首先要干掉表达式外面包着的多余的括号。

若表达式内不含任何符号,只有数字(或者是一个a),那么我们直接返回数字(或者a^1)。

否则,找到优先级最低的且不被括号包着的运算符。找最右边一个(因为我们的运算符都是左结合的)。

比如说 (a + 1) + (a + 2) * 2 - 5 中就是找到最右边的一个减号。

然后递归地,对该符号左右两边的表达式求值。求完值以后根据这个符号是什么做运算,然后返回。

需要注意的是getline读入换行符的问题以及某些数据可能出现的括号不匹配问题。代码里有注释有提到这些问题的解决方案。

代码比较长,但是可读性很高:

#include <string>
#include <vector>
#include <stack>
#include <unordered_map>
#include <iostream>
#include <map>
#include <charconv>

/*
    作者: ph3rin
    警告:本程序使用了c++17的特性。不过思路是通用的啦= =
    定义:
        自由运算符指没有被包在括号里的运算符
        我们不认为小括号是运算符
*/

int calc_power(int x, int y) {
    int ret = 1;
    for (int i = 0; i < y; ++i) {
        ret *= x;
    }
    return ret;
}

//存储已经求好值的表达式(按照完全的多项式展开储存)
struct EvaluatedExpr {
private:
    //这个map里面存储了已经求完值的表达式的信息
    //power[i]表示该表达式中a的i次方的系数
    //若不存在就默认为0(map的indexer还是很好用的,不存在的话访问上去就建一个0)
    std::map<int, long long> power;
public:
    EvaluatedExpr() = default;
    //一个工具函数,用来创建仅含有一个a^1的表达式值
    static EvaluatedExpr create_a() {
        EvaluatedExpr expr;
        expr.power[1] = 1;
        return expr;
    }
    EvaluatedExpr(const int val) {
        power[0] = val;
    }
    EvaluatedExpr(EvaluatedExpr&& rhs) noexcept(true) = default;
    EvaluatedExpr(const EvaluatedExpr& rhs) = default;
    EvaluatedExpr& operator = (EvaluatedExpr&& rhs) = default;
    EvaluatedExpr& operator = (const EvaluatedExpr& rhs) = default;
    //+-^*运算的定义。不要问我为什么乘法定义的是friend...(我就这么写了,打我啊!)
    EvaluatedExpr& operator+= (const EvaluatedExpr& rhs) {
        for (auto& pair : rhs.power) {
            power[pair.first] += pair.second;
        }
        return *this;
    }
    EvaluatedExpr& operator-= (const EvaluatedExpr& rhs) {
        for (auto& pair : rhs.power) {
            power[pair.first] -= pair.second;
        }
        return *this;
    }
    EvaluatedExpr& operator^= (const EvaluatedExpr& rhs) {
        EvaluatedExpr expr(1);
        int p = rhs.power.at(0);
        for (int i = 0; i < p; ++i) {
            expr = expr * (*this);
        }
        /*for (auto& pair : power) {
            expr.power[pair.first * p] += calc_power(power[pair.first], p);
        }*/
        *this = std::move(expr);
        return *this;
    }
    friend EvaluatedExpr operator* (const EvaluatedExpr& lhs, const EvaluatedExpr& rhs);
    //用于判断两个多项式是否等价。注意不能只遍历lhs的map,因为可能有lhs没有的次方数但是rhs有
    friend bool operator == (EvaluatedExpr& lhs, EvaluatedExpr& rhs);
};

EvaluatedExpr operator* (const EvaluatedExpr& lhs, const EvaluatedExpr& rhs) {
    EvaluatedExpr ret;
    for (auto& i : lhs.power) {
        for (auto& j : rhs.power) {
            ret.power[i.first + j.first] += (i.second * j.second);
        }
    }
    return ret;
}

bool operator == (EvaluatedExpr& lhs, EvaluatedExpr& rhs) {
    for (auto& pair : lhs.power) {
        if (rhs.power[pair.first] != pair.second) return false;
    }
    for (auto& pair : rhs.power) {
        if (lhs.power[pair.first] != pair.second) return false;
    }
    return true;
}

struct Expr {
    //一个储存运算符优先级的hash表。不想写switch了QAQ
    static std::unordered_map<char, int> const priority;
    //检查一个符号,若它是运算符,返回它的优先级;否则返回-1
    static int get_priotity(char op) {
        auto it = priority.find(op);
        if (it != priority.cend()) {
            return it->second;
        }
        return -1;
    }
    std::string data;
    //par_level[i]储存位于i的运算符外面包了多少层括号
    //par_pairs[i]储存了位于i的小括号对应的括号的位置
    std::vector<int> par_level, par_pairs;
    Expr() = delete;
    Expr(std::string expr_str) : par_pairs(expr_str.size(), -1),
        par_level(expr_str.size(), -1) {

        data.reserve(expr_str.size());
        for (char c : expr_str) {
            //这里我还是用了白名单
            //因为貌似getline会把换行符也弄进来= =RE了一下午
            if ((c >= '0' && c <= '9') || (priority.find(c) != priority.end()) || c == '(' || c == ')' || c == 'a') {
                data.push_back(c);
            }
        }

        std::stack<int> stk;
        int level = 0;
        for (int i = 0; i < data.size(); ++i) {
            switch (data[i])
            {
            case '(': {
                ++level;
                stk.push(i);
                break;
            }
            case ')': {
                //有坑爹的数据左右括号不匹配。此时无视右括号就可以了
                if (stk.empty()) {
                    break;
                }
                --level;
                int prev_left = stk.top();
                stk.pop();
                par_pairs[i] = prev_left;
                par_pairs[prev_left] = i;
                break;
            }
            default: {
                if (get_priotity(data[i]) != -1) {
                    par_level[i] = level;
                }
                break;
            }
            }
        }
    }
    int size() const { return data.size(); }
};

std::unordered_map<char, int> const Expr::priority{ {'+', 0}, {'-', 0}, {'*', 1}, {'^', 2} };

//ExprView表示一个子表达式
struct ExprView {
private:
    int par_level_reduce, start, count;
    const Expr& expr;
public:
    ExprView() = delete;
    ExprView(const Expr& expr) : ExprView(expr, 0, expr.size(), 0) {

    }
    ExprView(const Expr& expr, int start, int size, int reduce) : expr(expr), start(start),
        count(size), par_level_reduce(reduce) {
        //去掉最外层的括号
        while (this->expr.par_pairs[this->start] == this->start + this->count - 1) {
            ++this->start;
            this->count -= 2;
            //我们这个区间上所有运算符外面包着的括号层数-1
            ++par_level_reduce;
        }
    }
    char operator[] (int index) const {
        return expr.data[start + index];
    }

    char at(int index) const {
        return this->operator[](index);
    }

    int par_level(int index) const {
        return expr.par_level[start + index];
    }

    int size() const { return count; }

    //检查在index位置的字符是否是一个自由运算符,若是则返回它的优先级;否则返回-1
    int get_free_op_priotiy(int index) const {
        int p = Expr::get_priotity(at(index));
        if (p != -1 && par_level(index) - par_level_reduce == 0) {
            return p;
        }
        return -1;
    }

    //从右到左寻找优先级最低的自由运算符
    //之所以从右到左是因为我们的运算符都是左结合的!(划重点)
    int find_free_op() {
        int ret = -1, min_p = std::numeric_limits<int>::max();
        for (int i = count - 1; i >= 0; --i) {
            int p = get_free_op_priotiy(i);
            //如果是自由运算符 and 比当前找到的运算符优先级都要小
            if (p != -1 && p < min_p) {
                ret = i;
                min_p = p;
            }
        }
        return ret;
    }

    EvaluatedExpr evaluate() {
        int fp = find_free_op();
        //如果是没有自由运算符,说明这是一个a或者数字
        if (fp == -1) {
            int val = 0;
            if (at(0) == 'a') {
                return EvaluatedExpr::create_a();
            }
            else {
                //这是c++ 17的新特性
                //不用c++ 17的话嘛……会稍微麻烦一点
                //本质上就是把字符串某一个区间上的字串转化成整数
                std::from_chars(expr.data.data() + start, expr.data.data() + start + count, val);
                return EvaluatedExpr(val);
            }

        }
        //这里表示运算符左边和右边的子表达式的起点和长度
        int l_start = 0 + start, l_size = fp,
            r_start = fp + 1 + start, r_size = count - l_size - 1;
        //根据运算符进行运算
        switch (at(fp))
        {
        case '+': {
            return ExprView(expr, l_start, l_size, par_level_reduce).evaluate()
                += ExprView(expr, r_start, r_size, par_level_reduce).evaluate();
        }
        case '-': {
            return ExprView(expr, l_start, l_size, par_level_reduce).evaluate()
                -= ExprView(expr, r_start, r_size, par_level_reduce).evaluate();
        }
        case '*': {
            return ExprView(expr, l_start, l_size, par_level_reduce).evaluate()
                * ExprView(expr, r_start, r_size, par_level_reduce).evaluate();
        }
        case '^': {
            return ExprView(expr, l_start, l_size, par_level_reduce).evaluate()
                ^= ExprView(expr, r_start, r_size, par_level_reduce).evaluate();
        }
        default:
            throw std::runtime_error("Error!");
        }
    }
};

int main() {
    std::string expr_str;
    std::string ans;
    std::getline(std::cin, expr_str);

    Expr expr(expr_str);

    EvaluatedExpr&& question_ans = ExprView(expr, 0, expr.size(), 0).evaluate();
    ans.reserve(26);
    int choice_count;
    std::cin >> choice_count;
    std::string choice;
    //读入开头多余的换行
    std::getline(std::cin, choice);
    for (int i = 0; i < choice_count; ++i) {
        std::getline(std::cin, choice);
        Expr o_expr(choice);
        EvaluatedExpr&& option_ans = ExprView(o_expr, 0, o_expr.size(), 0).evaluate();
        if (question_ans == option_ans) {
            ans.push_back('A' + i);
        }
    }
    std::cout << ans << std::endl;
    return 0;
}