树分块学习笔记

lyz09-blog / 2023-08-19 / 原文

思想

树分块是一种能解决部分操作树上一条链的一种算法。

回忆下序列上的分块,其最精髓的地方在于将序列分成许多段,如果操作的区间包括了某一段,则直接使用整体处理这一段。我们也要使用某种方法使得操作的链也被分成许多块,但像 dfs 序等并不一定能保证整段的大小稳定。

先设定一个阈值 \(S\),我们要求每一段链的长度接近 \(S\)。一种方法是随机选取 \(\frac n S\) 个点,期望每一段的长度是 \(S\),但是太过玄学,便不使用这种方法。我们先处理出每个节点的深度及其祖先,若当前没有遍历的最深节点\(1\sim S\) 级祖先都没有被选取,则将其 \(S\) 级祖先选取。因为深度从大到小遍历,所以每个选取的点至少会覆盖 \(S\) 个节点,所以至多会有 \(\frac n S\) 个节点被选取,称这些被选取的点为关键点。而相邻两个关键点(需满足这两个点的 \(LCA\) 为其中一个点)间的链便相当于序列上分块的整段。

接下来处理出关键点间两两的答案(两点间仍需满足这两个点的 \(LCA\) 为其中一个点),为了防止查询时时间复杂度过大。预处理的时间复杂度为 \(O(\frac {n^2}{S^2}W)\),其中 \(W\) 为合并两段区间答案的复杂度。

剩下按照分块的套路,同时将整块答案与散块答案相加即可。具体而言,就是先找到 \(u,v\)\(LCA\),分别处理 \(u\sim LCA,v\sim LCA\) 即可。(有的题目中需要注意 \(LCA\) 不能被算两次)

实战

P6177 Count on a tree II/【模板】树分块

bitset 来表示有哪些颜色出现过,合并的时间复杂度为 \(O(\frac n \omega)\)