虚树
在解决树上的问题时,我们经常会查询点对之间的关系。如果直接在原树上进行询问,那么时间级别就会是o(n)级别的,但实际上原树上有许多不必要的点。如果将必要的点挑出来建立一颗新的树,在新树上查询的复杂度就基本为o(1)的了。我们把这样的树叫做虚树,它将两个点之间的链缩减为一条边,极大优化了时间。
那么如何构造呢?
我们预处理出节点的dfs序,令dfn表示dfs序,并将要处理的点以dfs序排序,并开一个栈维护右链。
维护右链指的是栈中存下虚树最右的一条链,这样可以保证虚树上的边就代表原树上的链。
维护右链
记栈顶为lit,当前遍历到的点为x。令 f=(lca(lit,x))
若 f=lit 则直接将x压入栈中。
这里有一显然的结论:f不可能是x
因为如果f是x,说明 dfn(f)=dfn(x)<dfn(lit) ,
而我们是按照dfs序号遍历的,于是 dfn(lit)<dfn(x) ,错误。
若lit和x在不同链上上则需详细分类
令pr为lit上一位。
循环3个操作
- dfn[pr]>=dfn[f] 将lit与pr连边,并弹栈。 当 dfn[pr]=dfn[f] 时退出循环。
- dfn[pr]<dfn[f] 说明f在lit与pr间,连边f—>pr,退栈,将f进栈,退出循环。
- 最后将x压入栈,保持dfs链。
这样的操作保证在栈中的点都在一条链上这就是维护右链。
最后将留在栈中的点依次与上一个点连边并退栈。虚树就构建完成了。
建树的复杂度为O(logn) 的 。
O(N)构造虚树
By kczno1
我做过的题里,都是读进来许多询问,之后节点总个数是O(N)的。 对每个询问,我们要将节点按dfs序排序,之后求出相邻两点的lca。 这两步都是nlogn的,也都可以离线做到O(n)。 排序,由于值域是1-n的,可以全部插到一个值域的数组里,记录是哪个询问的,再插回来就行了。 排完序后,哪些点需要求lca我们是知道的,于是可以用tarjan来求lca。