Loading

线段树合并

更好的博客

概念

字面意思。

对于两棵线段树,考虑依下合并:

  1. 从其中一棵的根结点向下遍历。

  2. 如果当前结点在两棵线段树中都存在,递归合并其整棵子树。

  3. 反之,若只在一棵线段树中存在,直接返回这棵线段树中的该结点。

  4. 若当前结点在两棵线段树中都不存在,则新树中也不存在该结点。

写成代码形式基本如下:

// 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]\) 范围内的结点数量即可。

维护深度

CF208E Blood Cousins

考虑转化为求结点 \(v\)\(p\) 级祖先除 \(v\) 外的 \(p\) 级后代数量,于是可以用线段树合并维护深度。

考虑维护:对于满足 \(v\)\(u\) 子树内的结点 \(u, v\),在计算结点 \(u\) 的答案时,对于每一个值 \(k\)\(1 \leq k \leq n\))维护 \(dep_{v} = k\) 的结点 \(v\) 的数量,于是只需用线段树查询深度为 \(p\) 的结点数量即可。

posted @ 2022-06-30 21:02  kymru  阅读(67)  评论(0编辑  收藏  举报