树上问题(长期更新)

虚树

在树上做一些东西的时候(废话),我们发现有很多树上的点是没有用的。

虚树就是对树上信息高度概括的技术,在虚树上,我们只保留最为有用的信息,且保持祖先后代关系不变。

具体而言,我们称一些点为关键点,即只有这些点会对问题的答案产生影响。

那么我们发现,关键点的LCA也是关键点。

当我们钦定树上的\(k\)个点作为关键点后,建出来的虚树最多有\(2k\)个点(感性理解)。

有一个事实:虚树里的点加多了也不会影响答案。这很显然。

那么为了方便,在建立虚树之前,我们把原树的树根\(1\)先加入虚树。

单调栈建立虚树

我们用栈维护虚树上的一条链,保证其中\(dfn\)单调递增。

起初,\(1\)在其中。

设现在的栈顶为\(tp\)

我们尝试加入一个点\(u\)

首先,找到\(l=LCA(u,tp)\)

  1. \(l=tp\)时,\(u\)就在这条链上,将\(u\)入栈即可。

  2. 否则,我们关注栈中次大结点(即栈顶下方的第一个元素)的\(dfn\)\(dfn_l\)的大小关系。当\(dfn_l\)比其小时,将\(tp\)与栈中次大节点连边并退栈,重复此过程。跳出循环后,若\(l\)就是次大节点,那么把栈顶与\(l\)连边并退栈,否则将栈顶与\(l\)连边并退栈,然后将\(l\)入栈。最后将\(u\)入栈。

最后栈中还剩一条链,拿出来建边即可。

常数很小,若只考虑连边,是\(O(k)\)的,\(k\)为关键点的数量。

细节很多。使用链式前向星时,对于每个入栈的点,清空它的表头head

二次排序+LCA连边建立虚树

首先,将关键点按\(dfn\)排序,并将任意相邻两个关键点求出LCA,将关键点和LCA都扔到数组\(A\)中。

由于\(dfn\)的性质,此时\(A\)中已经包含了所有虚树中的点(感性理解)。那么现在就是要按原树中的祖先后代关系建边。

我们把\(A\)再次按\(dfn\)排序,此时第一个点一定是虚树的根(但不一定是原树的树根\(1\),这一点与单调栈做法不一样,要注意一下)。我们对任意两个\(A\)中相邻的点\(A_i\)\(A_{i+1}\),连边\((\small LCA(A_i,A_{i+1}),A_{i+1}\big)\)

第一个点是虚树的根,所以后面的LCA中肯定有一个是它,所以不管它了。

为什么这样是对的?

  1. \(LCA(A_i,A_{i+1})=A_i\)时,由于已经按\(dfn\)排序,\(A_i\)\(A_{i+1}\)之间肯定没有其他关键点,所以直接连边就好。

  2. \(LCA(A_i,A_{i+1})\neq A_i\)时,同样由于已经按\(dfn\)排序,同上分析。

所以是对的。

常数较大。

使用时的细节

我们一般可以通过观察数据范围来判断是否在考察虚树(玄学)。

题目的数据范围里一般会有\(\sum\),最后的复杂度很可能是\(O(\sum k\log n)\)的。

虚树里的清空不能暴力去清,只能在进行计算/建树时顺带清空,这样才能保证复杂度。

树上合并类问题

dsu on tree

树分治

点分治

边分治

点分树

posted @ 2024-09-16 21:19  SilentPersuit  阅读(5)  评论(0编辑  收藏  举报