虚树学习笔记

观前须知:本博客中 \(k\) 均指关键点数量

-2 前置芝士

你需要会基本的dfs序(下简称dfn)及性质,并且要会写LCA(推荐树剖因为快)

-1 典型特征

关键点 \(\sum k \le1e5\) 之类的很小的数

虚树的特点是只保存关键点及其 LCA

0 引入

例:\(\color{green}CF613D\)

这个树上问题可以说非常符合 \(\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)\)

2 使用

就在虚树上跑 dp 或者 树剖之类奇奇怪怪的

注意的是清空虚树不能暴力,应该清空头指针即可,比如把用vector时把根节点clear了

例题只是让你看看虚树的题目长什么样,做法虚树上树形dp

提醒我在有实力的时候补上一些应用

posted @ 2023-07-27 17:04  exut  阅读(60)  评论(0编辑  收藏  举报
Title