树查询算法
本篇文章讲解树查询算法,树查询是指,查询某一条路径的信息,节点信息,子树信息等等。
树上倍增
我们定义 ancestor ( x , k ) \text{ancestor}(x,k) ancestor(x,k),为节点 x x x,向上走 k k k步的祖先节点,我们可以用树上倍增在 O ( n log n ) O(n \log n) O(nlogn)的时间内计算出来。
我们重新定义 ancestor ( x , k ) \text{ancestor}(x,k) ancestor(x,k)为节点 x x x,向上走 2 k 2^k 2k步的祖先节点。那么我们有DP公式:
ancestor ( x , k ) = ancestor ( ancestor ( x , k / 2 ) , k / 2 ) \text{ancestor}(x,k) = \text{ancestor}(\text{ancestor}(x,k / 2),k / 2) ancestor(x,k)=ancestor(ancestor(x,k/2),k/2)
则可以通过DFS遍历在在 O ( n log n ) O(n \log n) O(nlogn)内计算出节点的全部倍增信息。
如果我们想找 k − t h k-th k−th祖先,我们可以通过我们计算出的倍增数组进行二分逼近即可。详情查看博客树上倍增与LCA。
树遍历序数组
子树查询
我们对一颗树进行DFS遍历,并加入进入时间戳 d f n dfn dfn,那么对于子树 x x x,则其所有的孩子节点的 d f n dfn dfn必定是连续的。
例如子树 x x x的开始 d f n dfn dfn为 1 1 1,结束 d f n dfn dfn为 5 5 5,那么如果想求这个子树的和,我们直接对数组 1 − 5 1-5 1−5进行求和即可,这个过程可以使用树状数组,前缀和,线段树进行加速。
路径查询
我们定义 sum ( x ) \text{sum}(x) sum(x)为从根节点到节点 x x x路径上所有节点的和,如果我们更新一个节点的值,那么所有子树的 sum \text{sum} sum都要进行更新,因为子树的 d f n dfn dfn是连续的,我们可以通过树状数组,线段树进行更新。
树上差分
思想是将树看成是相交线段进行差分。分为点差分和边差分。通常需要结合LCA来实现。其实更推荐树链剖分,树链剖分可以很好的把LCA和线性结构组合在一起。
点差分
以统计一条路径上经过的点的次数为例子。
我们定义节点 x x x的差分变量:
diff x = val x − ∑ s ∈ son x val s \text{diff}_x = \text{val}_x - \sum_{s \in \text{son}_x} \text{val}_s diffx=valx−s∈sonx∑vals
我们有路径 δ ( s , t ) \delta(s,t) δ(s,t), l l l是LCA节点, f f f是LCA的父节点。
根据差分公式,我们有:
- diff s \text{diff}_s diffs加 1 1 1
- diff t \text{diff}_t difft加 1 1 1
- diff l \text{diff}_l diffl减 1 1 1
-
diff
f
\text{diff}_f
difff减
1
1
1
做好差分之后,通过一遍自底向上的DFS即可统计所有节点的权值。
边差分
如果要统计经过边的数量,我们需要把边权转换为点权,这样更好处理。
我们知道,一个父边只对应一个子节点,那么我们就可以把父边的权值转移到子节点上,如果一条边被激活,那么他所连接的子节点同样被激活。
我们根据上述差分公式,可以得出:
- diff s \text{diff}_s diffs加 1 1 1
- diff t \text{diff}_t difft加 1 1 1
- diff l \text{diff}_l diffl减 2 2 2
同样做好差分之后,通过一遍自底向上的DFS即可统计所有节点的权值,即边的权值。
例题
树链剖分
重链剖分
树链剖分一般指重链剖分,详情参考树链剖分。