2020 表达式 · 方案二 建树加关键性传播
先算每个子树的值,再判断翻转它会不会影响根
正文
// CSP-J 2020 复赛 T3 · 表达式(方案二 · 满分)
// 原题:https://oj.yecheng.tv/p/CSPJ2020C
//
// 方案二 · 建表达式树,只算一次 + 判断「这个变量说了算不算」
// 每次都重算太浪费了。真正要回答的其实是:翻转这个变量,根的值会不会跟着翻?
// 于是建一棵表达式树,做两遍遍历:
// 第一遍(自底向上):算出每个子树的值 val。
// 第二遍(自顶向下):传播「关键性」crit —— 这个子树的值一变,根的值会不会变。
// · 与节点 a & b:只有当另一边是 1 时,这边的变化才会影响结果;
// · 或节点 a | b:只有当另一边是 0 时,这边的变化才会影响结果;
// · 非节点 !a:这边一变结果必变,直接传下去。
// 根的 crit 设成 true(根变,答案当然变)。
// 最后:变量 xi 的答案 = 根的值 异或 (xi 所在节点是否关键)。
// 小技巧:节点是按「后序」创建的,子节点编号一定小于父节点,
// 所以顺着编号正着扫一遍算 val、倒着扫一遍传 crit 就够了,不用递归也不会爆栈。
#include <bits/stdc++.h>
using namespace std;
const int MAXNODE = 1000005;
struct Node {
int op; // 0 变量,1 与,2 或,3 非
int idx; // 变量下标(op 为 0 时有效)
int l, r; // 左右儿子编号
int val;
bool crit;
} tr[MAXNODE];
int cnt = 0;
int varNode[1000005];
int newNode() {
tr[cnt].op = 0;
tr[cnt].idx = 0;
tr[cnt].l = tr[cnt].r = -1;
tr[cnt].val = 0;
tr[cnt].crit = false;
return cnt++;
}
int main() {
freopen("expr.in", "r", stdin);
freopen("expr.out", "w", stdout);
string line;
getline(cin, line);
vector<string> tok;
stringstream ss(line);
string s;
while (ss >> s) tok.push_back(s);
stack<int> st;
for (const string& t : tok) {
if (t == "&" || t == "|") {
int b = st.top(); st.pop();
int a = st.top(); st.pop();
int u = newNode();
tr[u].op = (t == "&" ? 1 : 2);
tr[u].l = a;
tr[u].r = b;
st.push(u);
} else if (t == "!") {
int a = st.top(); st.pop();
int u = newNode();
tr[u].op = 3;
tr[u].l = a;
st.push(u);
} else {
int u = newNode();
tr[u].op = 0;
tr[u].idx = stoi(t.substr(1));
varNode[tr[u].idx] = u;
st.push(u);
}
}
int root = st.top();
int n;
cin >> n;
vector<int> v(n + 1);
for (int i = 1; i <= n; i++) cin >> v[i];
for (int u = 0; u < cnt; u++) { // 自底向上算值
if (tr[u].op == 0) tr[u].val = v[tr[u].idx];
else if (tr[u].op == 1) tr[u].val = tr[tr[u].l].val & tr[tr[u].r].val;
else if (tr[u].op == 2) tr[u].val = tr[tr[u].l].val | tr[tr[u].r].val;
else tr[u].val = !tr[tr[u].l].val;
}
tr[root].crit = true;
for (int u = cnt - 1; u >= 0; u--) { // 自顶向下传关键性
if (!tr[u].crit) continue;
if (tr[u].op == 1) {
if (tr[tr[u].r].val == 1) tr[tr[u].l].crit = true;
if (tr[tr[u].l].val == 1) tr[tr[u].r].crit = true;
} else if (tr[u].op == 2) {
if (tr[tr[u].r].val == 0) tr[tr[u].l].crit = true;
if (tr[tr[u].l].val == 0) tr[tr[u].r].crit = true;
} else if (tr[u].op == 3) {
tr[tr[u].l].crit = true;
}
}
int q;
cin >> q;
while (q--) {
int idx;
cin >> idx;
int flip = tr[varNode[idx]].crit ? 1 : 0;
cout << (tr[root].val ^ flip) << "\n";
}
return 0;
}
CSP-J 标程 · 复赛真题的其它内容
- 2019 数字游戏 · 方案一 逐字符统计
- 2019 数字游戏 · 方案二 二进制位运算
- 2020 优秀的拆分 · 方案一 贪心从大到小减
- 2020 优秀的拆分 · 方案二 直接看二进制位
- 2021 分糖果 · 方案一 枚举每个 k
- 2021 分糖果 · 方案二 看余数在哪一段
- 2022 乘方 · 方案一 边乘边判断
- 2022 乘方 · 方案二 快速幂加封顶
- 2023 小苹果 · 方案一 照规则真模拟
- 2023 小苹果 · 方案二 只盯住两个数字
- 2024 扑克牌 · 方案一 用集合去重
- 2024 扑克牌 · 方案二 二维布尔表
- 2025 拼数 · 方案一 收集后降序排序
- 2025 拼数 · 方案二 桶计数
- 2019 公交换乘 · 方案一 暴力匹配
- 2019 公交换乘 · 方案二 时间窗口优化
- 2020 直播获奖 · 方案一 每轮排序
- 2020 直播获奖 · 方案二 桶计数
- 2021 插入排序 · 方案一 每次真排一遍
- 2021 插入排序 · 方案二 增量维护有序表
- 2022 解密 · 方案一 二分求整数平方根
- 2022 解密 · 方案二 先估后校开方
- 2023 公路 · 方案一 朴素贪心
- 2023 公路 · 方案二 单调栈预处理
- 2024 地图探险 · 方案一 四方向分支写
- 2024 地图探险 · 方案二 方向数组
- 2025 座位 · 方案一 把座位表填出来
- 2025 座位 · 方案二 直接算排名
- 2019 纪念品 · 方案一 逐天完全背包
- 2019 纪念品 · 方案二 砍掉不赚钱物品
- 2020 表达式 · 方案一 每次重算后缀式
- 2021 网络连接 · 方案一 手写解析
- 2021 网络连接 · 方案二 读入后回拼校验
- 2022 逻辑表达式 · 方案一 递归分治
- 2022 逻辑表达式 · 方案二 递归下降
- 2023 一元二次方程 · 方案一 照公式逐步写
- 2023 一元二次方程 · 方案二 拆成小函数
- 2024 小木棍 · 方案一 先定位数再贪心
- 2024 小木棍 · 方案二 预处理最小数字表
- 2025 异或和 · 方案一 贪心能接就接
- 2025 异或和 · 方案二 动态规划加值域数组
- 2019 加工零件 · 方案一 递归加记忆化
- 2019 加工零件 · 方案二 奇偶最短路
- 2020 方格取数 · 方案一 深搜回溯
- 2020 方格取数 · 方案二 按列动态规划
- 2021 小熊的果篮 · 方案一 每轮扫一遍
- 2021 小熊的果篮 · 方案二 链表加有序集合
- 2022 上升点列 · 方案一 先只考虑不加点
- 2022 上升点列 · 方案二 二维动态规划
- 2023 旅游巴士 · 方案一 分层图加优先队列
- 2023 旅游巴士 · 方案二 状态压成一维
- 2024 接龙 · 方案一 按定义广搜
- 2024 接龙 · 方案二 滑动窗口逐轮推进
- 2025 多边形 · 方案一 枚举所有子集
- 2025 多边形 · 方案二 排序加计数 DP