把等价判断化为多点代入:递归下降求值器对 a=0..3 分别求值,四次取值全相等的选项即与题干恒等。
OJ: roj
题目 ID: 3543
难度:提高
标签:表达式解析递归下降数学python
创建: 2026-10-02 06:42
更新: 2026-10-10 11:30
形式化题目
给定一个关于变量 a、小于 + - * ^ 和括号组成,长度不超过 ^ * +/-,+ 与 - 同级,同优先级一律从左到右结合(包括 ^);^ 的指数是
要求找出所有与 A, B, C, …(无分隔符)。数据保证至少存在一个等价选项。
正解
思路
朴素做法是把每个表达式按 ^ 的指数最高
为什么可以只代入几个点? 判断恒等其实不需要完整的多项式:把
剩下的只是对一个具体数值求表达式的值。用经典的递归下降,按优先级分四层文法,每层一个函数:
expr := term ( (+|-) term )* ← 加减层,从左到右累加
term := power ( * power )* ← 乘层,从左到右连乘
power := atom ( ^ atom )* ← 幂层,左结合!2^3^2 = 64
atom := a | ( expr ) | 数字 ← 变量代入 x,括号递归回 expr每层用 while 循环"吃掉"同优先级的后续运算符,天然实现左结合;a 直接返回代入值
以样例 10 + (1 - a) + (a + 8) - (9 - 8) 代入
| 层 | 片段 | 计算过程 | 结果 |
|---|---|---|---|
| atom | a |
代入 |
|
| expr | (1 - a) |
||
| expr | (a + 8) |
||
| expr | (9 - 8) |
||
| expr | 整体 |
而选项 10+8 代入 IJ)。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string s;
int pos;
ll parse_expr(ll x);
ll parse_term(ll x);
ll parse_power(ll x);
ll parse_atom(ll x);
ll parse_expr(ll x) {
ll value = parse_term(x);
while (pos < (int)s.size() && (s[pos] == '+' || s[pos] == '-')) {
char op = s[pos]; pos++;
ll rhs = parse_term(x);
value = (op == '+') ? value + rhs : value - rhs;
}
return value;
}
ll parse_term(ll x) {
ll value = parse_power(x);
while (pos < (int)s.size() && s[pos] == '*') {
pos++;
value *= parse_power(x);
}
return value;
}
ll parse_power(ll x) {
ll value = parse_atom(x);
while (pos < (int)s.size() && s[pos] == '^') {
pos++;
ll e = parse_atom(x);
ll r = 1;
for (ll i = 0; i < e; i++) r *= value;
value = r;
}
return value;
}
ll parse_atom(ll x) {
if (s[pos] == 'a') { pos++; return x; }
if (s[pos] == '(') {
pos++;
ll v = parse_expr(x);
pos++; // 跳过 ')'
return v;
}
ll num = 0;
while (pos < (int)s.size() && isdigit((unsigned char)s[pos])) {
num = num * 10 + (s[pos] - '0');
pos++;
}
return num;
}
ll calc(const string& tokens, ll x) {
s = tokens;
// 去掉空格
string clean;
for (size_t i = 0; i < tokens.size(); i++)
if (tokens[i] != ' ') clean.push_back(tokens[i]);
s = clean;
pos = 0;
return parse_expr(x);
}
bool same(const string& expr, const string& other) {
for (ll x = 0; x < 4; x++)
if (calc(expr, x) != calc(other, x)) return false;
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<string> lines;
string line;
while (getline(cin, line)) {
// 去掉末尾 \r
if (!line.empty() && line.back() == '\r') line.pop_back();
lines.push_back(line);
}
// 处理可能带空行;第一行是标准表达式
int idx = 0;
while (idx < (int)lines.size() && lines[idx].empty()) idx++;
string std_expr = lines[idx];
idx++;
while (idx < (int)lines.size() && lines[idx].empty()) idx++;
int n = 0;
if (idx < (int)lines.size()) n = atoi(lines[idx].c_str());
idx++;
string out;
for (int i = 0; i < n; i++) {
while (idx < (int)lines.size() && lines[idx].empty()) idx++;
if (idx >= (int)lines.size()) break;
if (same(std_expr, lines[idx]))
out.push_back((char)('A' + i));
idx++;
}
cout << out << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 07:00
# update_at: 2026-10-02 07:00
import sys
OPS = "+-*^" # 四个可判定的二元运算符(左结合)
def calc(tokens: str, x: int) -> int:
"""求表达式在 a = x 处的值:运算符优先级 ^ > * > +/-,同级从左到右。"""
s = tokens.replace(" ", "") # 消掉多余空格,词法就只剩数、a、括号、运算符
pos = 0 # 全局扫描位置
def parse_expr() -> int:
"""加减层:把若干乘积项按从左到右的 +/- 累加。"""
nonlocal pos
value = parse_term()
while pos < len(s) and s[pos] in "+-":
op = s[pos]
pos += 1
rhs = parse_term()
value = value + rhs if op == "+" else value - rhs
return value
def parse_term() -> int:
"""乘层:把若干幂按从左到右的 * 连乘。"""
nonlocal pos
value = parse_power()
while pos < len(s) and s[pos] == "*":
pos += 1
value *= parse_power()
return value
def parse_power() -> int:
"""幂层:左结合的 ^,即 2^3^2 = (2^3)^2 = 64。"""
nonlocal pos
value = parse_atom()
while pos < len(s) and s[pos] == "^":
pos += 1
value **= parse_atom() # 指数 ≤ 10,快速幂都不需要
return value
def parse_atom() -> int:
"""原子:变量 a(代入 x)、括号子表达式或十进制数。"""
nonlocal pos
if s[pos] == "a":
pos += 1
return x
if s[pos] == "(":
pos += 1
value = parse_expr()
pos += 1 # 跳过配对的 ')'
return value
start = pos
while pos < len(s) and s[pos].isdigit():
pos += 1
return int(s[start:pos])
return parse_expr()
def same(expr: str, other: str) -> bool:
"""两式在 0,1,2,3 处取值都相同才认定等价(4 次多项式差异即可被识破)。"""
return all(calc(expr, x) == calc(other, x) for x in range(4))
def solve() -> None:
lines = sys.stdin.read().splitlines()
std = lines[0]
n = int(lines[1])
picks = [chr(ord("A") + i) for i in range(n) if same(std, lines[2 + i])]
print("".join(picks))
if __name__ == "__main__":
solve()复杂度
设表达式长度上界
总结
- 判断两个含变量表达式是否恒等,不必展开成多项式:挑
个不同的 值代入,两边取值都相等即可认定等价(不同多项式的公共根个数小于次数)。 - 求值用递归下降四层文法
expr → term → power → atom;^是左结合的(2^3^2 = 64),这是本题最容易踩的坑。 - Python 的精确大整数让最高达
量级的中间结果无需取模;同样的算法在 C++ 中需要对大质数取模实现。
图示解析
递归下降的分层消化
以 (a + 1 + 5) * a ^ 2 代入
expr ──► term ──► term ──► power ──► atom ──► ( expr ) ┐
│ │ │ 括号内重新
│ │ ▼ 从 expr 开始
│ │ a+1+5 = 3+1+5 = 9
│ ◄── 9 ^ atom = 9 ^ 2 = 81
│ ◄── 9 * 81 = 729 (乘层从左到右连乘)
└──► 729观察重点:atom 遇到 ( 时重新从 expr 层进入,括号内算完返回,外层的 power 再继续处理 ^ 2。乘层在两个幂