线段树合并:从入门到入土(附 5 道经典题)

线段树合并是处理“树上信息合并”的神器。别看它名字唬人,代码其实非常短。本文带你手撕模板,并给你 5 道必刷题。

核心思想

我们动态开点建线段树。对于两个线段树节点 xy,如果其中一个为空,直接返回另一个;否则递归合并左右儿子,最后 pushup 更新当前节点信息。

代码模板(以维护区间和为例)

int merge(int x, int y, int l, int r) {
    if (!x || !y) return x + y; // 其中一个为空,返回非空的那个
    if (l == r) {
        sum[x] += sum[y]; // 叶子节点合并
        return x;
    }
    int mid = (l + r) >> 1;
    lc[x] = merge(lc[x], lc[y], l, mid);
    rc[x] = merge(rc[x], rc[y], mid+1, r);
    sum[x] = sum[lc[x]] + sum[rc[x]];
    return x;
}

复杂度:合并两棵线段树的复杂度为 两棵树重叠的节点数,总复杂度 O(m log n)

5 道经典必刷题(按难度排序)

  1. P4556 [Vani有约会] 雨天的尾巴(入门板子,树上差分 + 线段树合并)
  2. P3521 [POI2011] ROT-Tree Rotations(逆序对 + 合并时统计贡献)
  3. P3302 [SDOI2013] 森林(主席树 + 启发式合并进阶)
  4. CF600E Lomsat gelral(树上众数,经典 DSU on tree 也可做,但线段树合并更优雅)
  5. P3605 [USACO17JAN] Promotion Counting P(动态开点权值线段树 + DFS 合并)

入土警告:线段树合并极易爆内存,一定要算好节点数量(一般 N * 4 不够,动态开点要开到 N * (logN) * 2 左右)。写完记得检查 merge 时有没有忘记 pushup

实战小贴士

  • 空间估算:对于 n 个点,每个点开一条链,总节点数约为 n * (⌈log₂n⌉ + 1),建议开 n * 20 起步。
  • 递归深度:线段树深度约 log n,递归合并时深度可控,一般不会爆栈。
  • 返回值merge 函数一定要返回合并后的根节点编号,否则会丢失信息。

本文标签:#数据结构 #线段树 #进阶 #合并