观前须知:本博客中 \(k\) 均指关键点数量
-2 前置芝士
你需要会基本的dfs序(下简称dfn)及性质,并且要会写LCA(推荐树剖因为快)
-1 典型特征
关键点 \(\sum k \le1e5\) 之类的很小的数
虚树的特点是只保存关键点及其 LCA
0 引入
这个树上问题可以说非常符合 \(\sum k\) 小
题面自己看,接下来考虑构建一个所谓的 虚树
1 构造
虚树的构建是一个增量算法,第一步是将 \(k\) 个关键点按dfn排序,按顺序一一加入(可以先加入根从而便于构造)
之后我们建一个栈(称为 \(stk\) ),我们希望这个栈可以保存一条从根出发的 不连续的路径
我们希望任意时刻,插入一个点 \(a[k]\) 后, \(stk[1]=root,stk[top]=a[k],stk[x]\) 是 \(stk[x-1]\) 后代
虚树上连边 \(u\to v\) 都是在 \(v\) 出栈时连边
现在考虑如何在已经建了一部分的虚树后插入一个新点 \(x\)
令 \(l\) 为 \(lca(x,stk[top])\)
-
\(l=stk[top]\) 直接在栈内加入 \(x\) 因为此时 \(x\) 在 \(stk[top]\) 子树内
-
\(l \neq stk[top]\) 寄,他不在咋搞
不写了tnnd
对于情况二,我们可以知道此时 \(x\) 不在栈顶的子树内
立马"回溯",弹栈直到 \(dep_{top-1} < dep_{l}\),此刻 \(lca\) 以下的原先的点都被打飞了,弹出的同时我们每次把 \(stk[top]\) 和 \(stk[top-1]\) 连边
但还有一件事,万一 \(l\) 不在虚树内呢?那就入栈
当所有点插入完全后,我们完全回溯,把栈弹干净同时连边
好耶,虚树建好了
这样为什么对呢?其实看一遍过程会发现显然我们所希望的 LCA 和关键点都在虚树内了,正确性是显然的
代码实现:
void ins(int x){
if(tp==0){//tp is 栈顶
st[++tp]=x;
return;//如果当前虚树空,无脑入栈
}
int L=lca(x,st[tp]);//如上文,栈顶和插入点lca,此处是原树上的lca
while(dep[L]<dep[st[tp-1]]){
add(st[tp-1],st[tp--]);
}
if(dep[L]<dep[st[tp]]) add(L,st[tp--]);
if(st[tp]!=L) st[++tp]=L;
st[++tp]=x;
return;
}
复杂度分析:
\(k\) 个关键点入栈一次出栈一次 \(O(k)\),LCA \(O(klogn)\),排序 \(O(klogk)\)
标签:top,LCA,笔记,stk,学习,lca,虚树,关键点 From: https://www.cnblogs.com/exut/p/17584807.html