虚树 virtual-tree

大致内容

oi-wiki上对虚树的介绍

如果一棵树有若干个询问,每次询问 \(k\) 个点,\(k\) 很小但 \(n\) 比较大时,真正需要处理的点很少。

在这种情况下,虚树就派上用场了。我们可以只处理那些关键点,而忽略其他点,方便处理 \(\sum k\)\(n\) 同阶的情况。

那么哪些点才是需要的呢?发现只有所有节点和它们两两之间的 \(Lca\) 可能会用到,所以关键点数量一定不超过 \(2k\) 个。

那么如果可以建立出这么一棵最多只有 \(2k\) 个点的树,就可以快速解决问题了。

建立方法

假设我们需要加入一个新的节点,显然它最终会接到虚树的一条链上。

考虑将所有需要加入的按照 dfn 序排序,依次加入,并维护虚树上根到上一次维护的点的路径。

接待来开始分讨:

  • 如果下一个点直接在路径之后,直接接上。
  • 否则,设根到上一次处理的点到这一次处理的点的路径交点为 \(Lca\)
    • 如果 \(Lca\) 在上一条路径中,那么简单的弹出 dfn 比 \(Lca\) 大的即可。
    • 如果 \(Lca\) 不在路径中,发现会出现 \(F - Lca - x\) 的情况,如果暴力弹出了 \(x\),就会连上 \(F - x\) 的边。所以只能弹出这个点的父亲的 dfn 比 \(Lca\) 大的点,连上 \(Lca - x\) 之后再弹出 \(x\)

最后的话在把存的路径上的点连边即可。

\(\bigstar\texttt{attention}\):由于整个图需要用到的边与点很少,所以在每次新建虚树的时候不能全局清空,而是在把一个新的点加入栈中的时候清空这个点连过的边。

\(\bigstar\texttt{attention}\):一定要在将这个点加入栈的时候清空这个点的连边情况!!!!不能早也不能晚。

建边代码:

inline void build(int s)
{
	 sort(dot+1,dot+m+1,cmp),tot=0;
	 sta[tp=1]=1,hea[1]=0;
	 for(int i=1,l;i<=m;i++)
	 {
	 	 if(dot[i]==1) continue; // 别忘了 1! 
	 	 l=Lca(sta[tp],dot[i]);
	 	 if(l!=sta[tp])
	 	 {
	 	 	 while(dfn[l]<dfn[sta[tp-1]])
	 	 	 	 add(sta[tp-1],sta[tp],dist(sta[tp]-sta[tp-1])),tp--;
	 	 	 if(sta[tp-1]==l)
	 	 	 	 add(l,sta[tp],dist(sta[tp]-l)),tp--;
	 	 	 else
	 	 	 	 hea[l]=0,add(l,sta[tp],dist(sta[tp]-l)),sta[tp]=l;
		 }
		 hea[dot[i]]=0,sta[++tp]=dot[i];
	 }
	 for(int i=1;i<tp;i++) add(sta[i],sta[i+1],dist(sta[i+1]-sta[i]));
}

建立完之后就可以在虚树上处理答案了,这样即使多次询问,复杂度也是和选中关键点个数同阶的。

例题

\(\bigstar\texttt{Trick}\):在虚边上连出去的那些不需要的点改怎么算入贡献?一种比较通用方法是预处理从每个点出发向上 \(2^k\)、向下 \(2^k\) 深度的答案最值,然后对于虚点的父子关系判断一下分别能够扩展的深度即可。

P2495 [SDOI2011]消耗战

P3233 [HNOI2014]世界树

P4103 [HEOI2014]大工程

P7737 [NOI2021] 庆典

先搞清楚那个奇怪的道路连通性限制到底有什么用。

首先将图用强联通分量缩点,这些点一定是一起选择的。

接下来有一个性质,如果我们定义一个连通块 \(G\) 为一个半联通分量当且仅当对于 \(\forall x,y\in G\),一定存在 \(x\Rightarrow y\)\(y\Rightarrow x\)

那么在一个大小为 \(s\) 的半联通分量中,可以给每一个点分配一个排名 \(rk_i\),满足所有 \(s\) 个点的 \(rk\) 能形成一个排列,且如果存在 \(x,y\in G,rk_x<rk_y\),一定有 \(x\Rightarrow y\)

那么每一个半联通分量完全可以抽象为一条链,最终整张图一定可以抽象为一个以某个点为根的外向树。

即用拓扑序完成建树工作。

那么 \(k=0\) 的情况就非常好解决了,直接判断 \(s\) 是否能够到达 \(t\),输出路径上的点权之和。

考虑 \(k=1\) 怎么做,就是在一棵外向树上加上了一条边:

  • 如果加入了一条反祖边,可以直接将形成的环视为一个点,继续判断。
  • 如果加入了一条横叉边,根据树的结构直接继续判断即可。

如果 \(k=2\),情况就比较复杂了,直接上虚树解决一切问题。

P4426 [HNOI/AHOI2018]毒瘤

给定 \(n\) 个点 \(m\) 条边的无向图,\(m\le n+10\),保证所有点之间互相可达。

我们需要从中选出若干个点,要求相连的点不能同时选择,问选择方案数对 998244353 取模的结果。

\(n\le 10^5\)

如果给定的是一棵树,那直接 dp,期望得分 \(10pts\)

如果多余的边比较少的话,可以 \(3^n\) 枚举非树边的选择情况,继续 dp。假设我们选择了一条非树边,整张图的选择情况就是第一个点不选 + 第二个点不选 - 两个点都不选。期望得分 \(55-85pts\)

考虑将非树边两端的点作为关键点,建立虚树,考虑计算两点之间以及关键点伸出去的子树的方案。

这样当我们枚举的时候就可以快速捞取信息了,每次检查的量大约是 \(50\) 个点左右,加上 \(3^{11}\) 枚举,我试了,是对的。打完 \(6.1k\) 代码跑路。

P5360 [SDOI2019]世界地图

在喵咪星球上有 \(n\) 条纬线和 \(m\) 条经线,星球南北极不连通,东西联通。

每一个纬线和经线的交点都是一个国家,相邻的国家之间都有道路连接。

然而喵咪星并不太平,在第 \(i\) 个世纪时,维度在 \([l_i,r_i]\) 之间的国家都会参加这个世纪的世界大战,而其他国家都是安全的。

如果一条道路连接两个安全的国家,那么这是一条安全的道路。

但是维护一条安全的道路需要 \(v_k\) 的费用。

现在需要开放一些安全的道路使得所有安全的国家都直接或间接联通(容易发现一定是一棵树)。

其中 \(l_i\le r_i\)

子任务 \(1\)\(n=1,m=10000,v_k\le 10^9,q=300000\)

子任务 \(2\)\(n=2,m=10000,v_k\le 10^9,q=300000\)

子任务 \(3\)\(n=100,m=10000,v_k\le 10^9,q=10000\)

\(\text{Subtask~1}\) 直接前缀和,考虑 \(\text{Subtask~2}\) 怎么做:好像需要每次 \(O(n)\) 求解。

后面不会了,去看题解了。。。

咕咕咕~

posted @ 2021-10-01 14:53  EricQian06  阅读(59)  评论(0编辑  收藏  举报