虚树学习笔记

发布时间 2023-05-31 18:01:53作者: lizhous

概念

虚树是一棵树,相对于原树而言。它删去原树上某些点,再按原树父子关系连边构成的树。

它对树上算法有一定优化。假如一个树上问题仅与部分节点有关,如树形DP,DP值仅在部分节点有改变,那么就可以已这部分节点建成虚树,省略其他部分,复杂度为部分节点总和。

例:消耗战

本题的删边DP只与一点到顶点的最小边权边有关,但是要保证每个关键点的祖先都被计算。建虚树并DP是很好的选择。

建法

首先虚树保留关键点和所有他们的 lca,包括 lca 的 lca。发现两个关键点之间可能中途插入点。所以使用栈,反转扫描和建边的过程。

整个过程先将节点按dfn序排序,保证栈内存储节点在一条链上。若下一节点不在同一链上,则开始从栈顶删点直到栈次顶与当前节点在一条链上,注意弹出时要和次顶建边。此时栈顶向当前点与其的 lca 建边并弹出。现在的栈顶要么是刚才建边的 lca,就可以不管。否则就要压入建边的 lca 以保证两点 lca 有边。

int st[1000001];
vector<edge> f[1000001];
void btxs()
{
	int top=0;
	st[++top]=1;
	for(int i=1;i<=k;i++)
	{
		int lca=getlca(st[top],din[i]);
		while(dfn[st[top]]>=dfn[lca]&&top>1)
		{
			if(dfn[st[top-1]]<dfn[lca]) break;
			f[st[top-1]].push_back((edge){st[top],0/*边权*/});
			top--;
		}
		if(lca==st[top])
		{
			st[++top]=din[i];
		}
		else
		{
			f[lca].push_back((edge){st[top],0});
			top--;
			st[++top]=lca;
			st[++top]=din[i];
		}
	}
	while(top>1)
	{
		f[st[top-1]].push_back((edge){st[top],0});
		top--;
	}
}