先建树,再沿着会执行的路径求值
每个运算符都先算左边。只有左边不能决定答案时,才进入右边。
0 & x 一定是 0,所以 x 不会被计算
1 | x 一定是 1,所以 x 不会被计算
1 | x 一定是 1,所以 x 不会被计算
本节课只抓一个关键问题:表达式可以算出结果,但短路次数必须按“真正被执行的路径”统计。
每个运算符都先算左边。只有左边不能决定答案时,才进入右边。
先看短路规则,再用样例追踪计数变化,最后把规则落到两个栈建树和非递归求值代码中。
数字是叶子结点,`&` 和 `|` 是内部结点。整道题就是在树上求根的值。
运算符栈处理优先级,结点栈保存已经形成的子树。每个字符只处理常数次。
求值时先进入左子树。若左值已经触发短路,就不进入右子树,也不统计右子树内部。
| 当前运算 | 左边的值 | 动作 | 计数 |
|---|---|---|---|
a & b |
0 |
答案已经是 0,跳过右边 b |
& 短路 +1 |
a & b |
1 |
必须继续计算右边 b |
不加 |
a | b |
1 |
答案已经是 1,跳过右边 b |
| 短路 +1 |
a | b |
0 |
必须继续计算右边 b |
不加 |
0&(1|0)|(1|1|1&0)不是在数表达式里出现了多少个 0& 或 1|。
而是在模拟程序真正的执行顺序:左边先算,右边可能根本不会被看见。
1&0 本身会短路吗?如果单独计算会。但它被外层 | 跳过,所以本题不能统计它。
1|(0&1) 的统计结果是?0&1&1 有几次 & 短路?| 被计算的部分 | 判断 | 当前结果 | 计数变化 |
|---|---|---|---|
0&(1|0) |
左边是 0,右边整段跳过 | 0 |
& 加 1 |
0 | 右边 |
左边是 0,不能短路 | 继续计算右边 | 不变 |
1|1 |
左边是 1,右边跳过 | 1 |
| 加 1 |
(1|1) | (1&0) |
左边已经是 1,右边整段跳过 | 1 |
| 再加 1 |
| 整式 | 0|1 = 1 |
1 |
最终 1 2 |
#include <bits/stdc++.h>
using namespace std;
struct Node {
char op;
int l, r;
};
int priorityOf(char c) {
if (c == '|') return 1;
if (c == '&') return 2;
return 0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin >> s;
vector<Node> tree;
vector<int> nodes;
vector<char> ops;
tree.reserve(s.size() + 5);
nodes.reserve(s.size() + 5);
ops.reserve(s.size() + 5);
auto newNode = [&](char op, int l = -1, int r = -1) {
tree.push_back({op, l, r});
return (int)tree.size() - 1;
};
auto build = [&](char op) {
int r = nodes.back();
nodes.pop_back();
int l = nodes.back();
nodes.pop_back();
nodes.push_back(newNode(op, l, r));
};
for (char c : s) {
if (c == '0' || c == '1') {
nodes.push_back(newNode(c));
} else if (c == '(') {
ops.push_back(c);
} else if (c == ')') {
while (!ops.empty() && ops.back() != '(') {
char op = ops.back();
ops.pop_back();
build(op);
}
ops.pop_back();
} else {
while (!ops.empty() && ops.back() != '(' &&
priorityOf(ops.back()) >= priorityOf(c)) {
char op = ops.back();
ops.pop_back();
build(op);
}
ops.push_back(c);
}
}
while (!ops.empty()) {
char op = ops.back();
ops.pop_back();
build(op);
}
int root = nodes.back();
vector<unsigned char> val(tree.size(), 0);
long long cntAnd = 0, cntOr = 0;
struct Frame {
int u;
int state; // 0: 先算左子树,1: 判断是否短路,2: 已算右子树
};
vector<Frame> st;
st.reserve(tree.size());
st.push_back({root, 0});
while (!st.empty()) {
Frame &f = st.back();
Node &cur = tree[f.u];
if (cur.op == '0' || cur.op == '1') {
val[f.u] = cur.op - '0';
st.pop_back();
} else if (f.state == 0) {
f.state = 1;
st.push_back({cur.l, 0});
} else if (f.state == 1) {
int leftValue = val[cur.l];
if (cur.op == '&' && leftValue == 0) {
cntAnd++;
val[f.u] = 0;
st.pop_back();
} else if (cur.op == '|' && leftValue == 1) {
cntOr++;
val[f.u] = 1;
st.pop_back();
} else {
f.state = 2;
st.push_back({cur.r, 0});
}
} else {
if (cur.op == '&') {
val[f.u] = val[cur.l] & val[cur.r];
} else {
val[f.u] = val[cur.l] | val[cur.r];
}
st.pop_back();
}
}
cout << (int)val[root] << '\n';
cout << cntAnd << ' ' << cntOr << '\n';
return 0;
}
priorityOf(ops.back()) >= priorityOf(c) 保证同级从左到右,且 & 比 | 先结合。
只要不把右子树压入求值栈,它内部所有短路自然都不会被统计。
外层已经跳过的部分不能统计。先问“它有没有被执行到”。
0|1&0 等价于 0|(1&0),不是 (0|1)&0。
本题长度到 10^6,课堂上可以讲递归思想,但提交代码更稳的是手写栈。