1 条题解
-
0
🔍 题目核心思路分析
问题理解
题目描述了一个三叉树结构,其中每个非叶子节点有三个输入(来自子节点或外部输入),其输出由三个输入的多数表决决定(即1的数量多则输出1,否则输出0)。
- 树结构:有
n个非叶子节点(编号1~n),2n+1个叶子节点(编号n+1~3n+1) - 操作:
q次修改,每次翻转一个叶子节点的值(0变1,1变0),要求输出每次修改后根节点的输出
关键观察
-
节点状态定义:对每个节点
u,定义val[u]为其三个输入中1的个数- 节点输出:
out[u] = 1当且仅当val[u] ≥ 2 - 对于叶子节点,初始
val值为2 * input(即0或2)
- 节点输出:
-
修改传播规律:
- 当叶子节点从
0变为1时,只会影响从该叶子节点父节点开始向上连续val=1的链 - 当叶子节点从
1变为0时,只会影响从该叶子节点父节点开始向上**连续val=2的链` - 一旦遇到
val不为1(或2)的节点,传播停止
- 当叶子节点从
算法选择:LCT(Link-Cut Tree)
由于需要高效处理树上路径操作,选择使用LCT,复杂度
O((n+q) log n)。💡 LCT解法详细说明
维护信息
每个节点维护:
val:该节点三个输入中1的个数(0~3)n1:当前splay子树中深度最大的val ≠ 1的节点n2:当前splay子树中深度最大的val ≠ 2的节点tag:懒标记,表示子树是否需要val翻转(1↔2)
核心操作
-
初始建树:用拓扑排序计算每个节点的初始
val -
修改叶子节点x:
- 找到x的父节点f
- 根据x是0→1还是1→0,确定要找
n1[f]还是n2[f] access(f)后splay(f),找到链顶节点- 对受影响的部分打标记修改
代码实现要点
// 核心更新函数 void pushup(int x) { // 优先右子树,然后当前节点,最后左子树 n1[x] = n1[ch[x][1]]; if (!n1[x]) n1[x] = (val[x] != 1) ? x : 0; if (!n1[x]) n1[x] = n1[ch[x][0]]; n2[x] = n2[ch[x][1]]; if (!n2[x]) n2[x] = (val[x] != 2) ? x : 0; if (!n2[x]) n2[x] = n2[ch[x][0]]; } // 标记下传 void pushdown(int x) { if (tag[x]) { if (ch[x][0]) update_node(ch[x][0], tag[x]); if (ch[x][1]) update_node(ch[x][1], tag[x]); tag[x] = 0; } } // 节点更新 void update_node(int x, int d) { tag[x] += d; if (d == 1) { // 1→2 if (val[x] == 1) val[x] = 2; else if (val[x] == 2) val[x] = 1; } else { // 2→1 if (val[x] == 2) val[x] = 1; else if (val[x] == 1) val[x] = 2; } swap(n1[x], n2[x]); }🧮 复杂度分析
- 预处理:O(n) 拓扑排序
- 每次查询:O(log n) LCT操作
- 总复杂度:O(n + q log n),可满足n,q ≤ 5×10⁵
💎 总结
本题的关键在于发现修改传播的连续性(只影响val=1或2的连续链),从而可以用LCT高效维护。需要注意的细节是叶子节点的val初始为0或2,而非0或1。
- 树结构:有
- 1
信息
- ID
- 5586
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者