AIGC标识 P11127 题解

P11127 [ROIR 2024] 二叉树的遍历 (Day 2) 题解总结

一、题意简述

给定一棵 n 个节点的二叉树,节点编号 1 ~ n,根为 1。
每个节点有一个值 x ∈ {-1, 0, 1},决定遍历时输出该节点的时机:

  • x = -1:在左右子树之前输出(前序)
  • x = 0:在左子树之后、右子树之前输出(中序)
  • x = 1:在左右子树之后输出(后序)

初始所有节点 x = -1。

需要支持 q 次操作:

  1. 将节点编号区间 [l, r] 的 x 改为指定值;
  2. 查询节点 i 在当前遍历中的位置。

数据范围:n, q ≤ 10^5。


二、核心思路

1. 位置分解公式

对于任意节点 u,它在遍历中的位置可以拆成三部分:

pos(u) = 1 + lef[u] + D(u) + self(u)

其中:

  • lef[u]:静态贡献,只与树结构有关;
  • D(u):动态贡献,由所有祖先节点的 x 值决定;
  • self(u):自身贡献,由 u 自己的 x 值决定。

2. 静态贡献 lef[u]

在 DFS 过程中计算:

  • 若 u 是父节点 p 的左孩子:
    lef[u] = lef[p]
  • 若 u 是父节点 p 的右孩子:
    lef[u] = lef[p] + sz[ls[p]]

含义:从根走到 u 的过程中,每次向右走,都要把左兄弟的整棵子树排在前面。

3. 动态贡献 D(u)

遍历所有祖先 v,累加贡献:

  • 若 x_v = -1:只要 u 在 v 的子树中,贡献 +1;
  • 若 x_v = 0 且 u 在 v 的右子树中,贡献 +1;
  • 其他情况贡献 0。

用 DFS 序表示:

  • x_v = -1:对区间 [dfn[v]+1, tout[v]] 加 1;
  • x_v = 0:对区间 [dfn[rs[v]], tout[rs[v]]] 加 1。

注意:第一个区间从 dfn[v]+1 开始,是为了排除 v 自己。

4. 自身贡献 self(u)

  • 若 x_u ≠ -1:加上左子树大小 sz[ls[u]];
  • 若 x_u = 1:再加上右子树大小 sz[rs[u]]。

三、关键转化

转化 1:把“遍历位置”转化为“祖先贡献 + 自身贡献”

原本遍历顺序依赖整棵树,直接模拟不可行。
通过分析每个祖先对后代位置的影响,把全局顺序拆成独立的祖先贡献,使得动态修改 x 时只需要维护每个祖先对后代的区间贡献。

转化 2:把“祖先贡献”转化为“DFS 序上的区间加”

在 DFS 序中,一个节点的子树是一段连续区间。
因此:

  • x_v = -1 对整棵子树贡献 +1;
  • x_v = 0 只对右子树贡献 +1。

这样就把“树上的祖先关系”转化成了“DFS 序上的区间加、单点查询”。

转化 3:按节点编号分块

修改操作是对节点编号区间 [l, r] 赋值,而查询依赖 DFS 序。
两个维度不一致,直接用线段树很难同时维护。

于是按节点编号分块,每块大小 B ≈ √n。

每个块维护:

  • tag[b]:块统一标记;
  • val[u]:块不统一时每个节点的实际值。

这样修改时可以:

  • 整块直接改 tag;
  • 散块暴力下推后修改。

转化 4:预处理整块贡献数组 f, g

对于每个块 b,预处理:

  • f[b][i]:假设块内所有节点 x = -1,对 DFS 序为 i 的节点的祖先贡献总和;
  • g[b][i]:假设块内所有节点 x = 0,对 DFS 序为 i 的节点的祖先贡献总和。

计算方式:块内每个节点做区间加,最后前缀和。

查询时整块贡献可以 O(1) 查表。

转化 5:用值域分块维护散块贡献,避免退化

如果很多块变成“不统一”,查询时暴力扫描这些块会退化成 O(n)。

于是引入值域分块(差分思想)维护所有散块节点的贡献:

  • vcnt[i]:DFS 序位置 i 的差分值;
  • vsum[b]:值域块 b 的差分和。

操作:

  • 区间加 O(1);
  • 单点查询 O(√n)。

修改时散块节点从值域结构中删旧值、加新值;整块若原本不统一,也先把贡献移出值域结构,再改 tag。

查询时散块贡献由一次 vqry(dfn[pos]) 得到,不再暴力扫描。


四、算法流程

1. 预处理

  1. 读入左右孩子 ls[u], rs[u];
  2. 从根节点 1 开始 DFS:
    • 计算 dfn[u]:DFS 序;
    • 计算 tout[u]:子树结束位置;
    • 计算 sz[u]:子树大小;
    • 计算 lef[u]:静态贡献。

2. 分块初始化

  1. 按节点编号分块,块长 B = √n;
  2. 对每个块:
    • 设置 st[b], en[b];
    • 标记 tag[b] = -1;
    • 调用 build_block(b) 计算 f、g 数组。
  3. 初始化 val[u] = -1;
  4. 初始化值域分块。

3. 区间修改 [l, r] → x

  1. 若 l, r 在同一块:
    • 下推该块标记;
    • 暴力修改 val[l..r];
    • 将该块标记为不统一 tag = -2;
    • 同时更新值域分块中的散块贡献。
  2. 若跨块:
    • 处理左散块:下推、暴力修改、标记不统一、更新值域结构;
    • 处理右散块:同理;
    • 中间整块:若原本不统一,先把块内节点贡献移出值域结构,再直接 tag = x。

4. 单点查询 pos

  1. 初始化:
    ans = 1 + lef[pos]
  2. 获取 pos 当前 x 值:
    • 若所在块 tag != -2,取 tag;
    • 否则取 val[pos]。
  3. 加上自身贡献:
    • 若 x_pos ≠ -1,加 sz[ls[pos]];
    • 若 x_pos = 1,加 sz[rs[pos]]。
  4. 加上散块动态贡献:
    • ans += vqry(dfn[pos])。
  5. 遍历所有块:
    • 若 tag[b] == -1:ans += f[b][dfn[pos]];
    • 若 tag[b] == 0:ans += g[b][dfn[pos]];
    • 若 tag[b] == 1:无贡献;
    • 若 tag[b] == -2:散块贡献已由值域分块处理,不再扫描。
  6. 输出 ans。

五、注意事项

  1. lef 必须计算
    缺少 lef 会导致静态贡献丢失,查询结果错误。

  2. build_block 中区间从 dfn[i]+1 开始
    排除节点自己,否则会把自身也当作祖先。

  3. 暴力检查祖先时用 dfn[j] < dfn[pos]
    严格小于,排除自己。

  4. 判断右子树时用 dfn[rs[j]] <= dfn[pos]
    包含右孩子本身,因为 x=0 时右孩子本身也在右子树中。

  5. 值域分块防止退化
    不统一块如果暴力扫描,最坏 O(n)。用值域分块后,散块贡献查询 O(√n),整体稳定。

  6. f、g 数组转置
    存成 f[dfn][块] 而非 f[块][dfn],查询时 f[dp][i] 连续访问,缓存友好。

  7. 块长选择
    B = √n 通常足够。若查询偏多,可适当调大块长(如 1.2√n)减少块数。

  8. 内存控制
    f、g 各约 n × (n/B) 个 short,总内存约 130 MB。若用 int 会超内存。

  9. 快读快写
    大数据量下 cin/cout 可能超时,建议用 fread / fwrite 手写读写。

  10. 严格祖先与自身贡献不能重复
    自身贡献单独加,祖先贡献只统计严格祖先。


六、复杂度

步骤 复杂度
DFS 预处理 O(n)
分块预处理 f、g O(n√n)
区间修改 O(√n)
单点查询 O(√n)
总时间 O((n+q)√n)
空间 O(n√n),约 130 MB

在 n, q ≤ 10^5 下,约 3 × 10^7 次操作,可稳定通过。


七、代码实现要点

  • 用 short 存 f、g,节省内存;
  • val 和 tag 可用 signed char;
  • 热点函数加 inline 或 attribute((always_inline));
  • 查询时提前取出 short *fd = f[dp], *gd = g[dp];
  • 值域分块和节点分块使用同一块长,简化边界;
  • 修改散块时,先 modify(i, -1) 删旧值,改 val,再 modify(i, 1) 加新值;
  • 整块从不统一变为统一时,先把块内所有节点贡献移出值域结构。

八、总结

本题的关键在于:

  1. 把遍历位置拆解为静态贡献、动态贡献、自身贡献;
  2. 把祖先贡献转化为 DFS 序区间加;
  3. 按节点编号分块,预处理整块贡献数组 f、g;
  4. 用值域分块维护散块贡献,避免不统一块暴力扫描导致的退化;
  5. 卡常优化:数组转置、快读快写、强制内联、short 省内存。

掌握这五个关键转化,即可在 O((n+q)√n) 时间内解决本题。

posted @ 2026-10-04 21:41  xmd11  阅读(8)  评论(0)    收藏  举报