线段树合并
概念
字面意思。
对于两棵线段树,考虑依下合并:
-
从其中一棵的根结点向下遍历。
-
如果当前结点在两棵线段树中都存在,递归合并其整棵子树。
-
反之,若只在一棵线段树中存在,直接返回这棵线段树中的该结点。
-
若当前结点在两棵线段树中都不存在,则新树中也不存在该结点。
写成代码形式基本如下:
// a 为树 1 中的该结点,b 同理
// l, r 是该线段树结点对应的区间
int merge(int a, int b, int l, int r)
{
if (!a) return b;
if (!b) return a;
if (l == r)
{
// merge
return a;
}
int mid = (l + r) >> 1;
ls[a] = merge(ls[a], ls[b], l, mid);
rs[a] = merge(rs[a], rs[b], mid + 1, r);
push_up(a);
return a;
}
一般认为遍历一次树并进行合并的复杂度大约是 \(O(n \log n)\),但是据说可以被卡。
应用
一般用于处理一部分维护子树的问题,并且通常是值域线段树。
如果对于每一棵子树都维护一棵线段树可以解决问题,不妨考虑线段树合并。
维护子树
维护子树值域
P3605 [USACO17JAN]Promotion Counting P
考虑先对 \(p\) 离散化。用线段树合并维护:对于满足 \(v\) 在 \(u\) 子树内的结点 \(u, v\),在计算结点 \(u\) 的答案时,对于每一个值 \(k\)(\(1 \leq k \leq n\))维护 \(p_{v} = k\) 的结点 \(v\) 的数量。然后直接查询值在 \([p_{i} + 1, n]\) 范围内的结点数量即可。
维护深度
考虑转化为求结点 \(v\) 的 \(p\) 级祖先除 \(v\) 外的 \(p\) 级后代数量,于是可以用线段树合并维护深度。
考虑维护:对于满足 \(v\) 在 \(u\) 子树内的结点 \(u, v\),在计算结点 \(u\) 的答案时,对于每一个值 \(k\)(\(1 \leq k \leq n\))维护 \(dep_{v} = k\) 的结点 \(v\) 的数量,于是只需用线段树查询深度为 \(p\) 的结点数量即可。