树形$dp$利器——"$fake$"树(虚树$qwq$)


 前置知识:

$1、$$dfs$序

$2、$倍增法或者树链剖分求$lca$


 问题引入:

在许多的树形动规中,很多时候点特别多,而又有一些毒瘤操作,导致很多时候,原本优秀的算法变得很鸡肋,而虚树就是解决这种问题的一把利器

那让我们来看一道例题:

洛谷P2495 [sdoi2011]消耗战

一句话题意:给定一棵$n$个节点的树,$m$次询问,每次给出几个点,要你删除若干条边使得这些点不和根节点联通

我们看到数据范围:

$n<=2e5,\sum k<=5e5$

 考虑朴素的$dp$,状态和方程都很好想,大概就是:
对于一个点,可以删除它到根节点的边的最小值,或者是把子树中连向标记点的链中最小的边删除(当然还要考虑当前点是不是标记点等因素,不过这不是重点$qwq$)
那么一次询问就是$O(n)$,看上去确实很优秀了,但是有$m(m<=5e5)$次询问,那么总时间复杂度就是$O(nm)$的算法了,$m$大一点就会炸得飞起,然而其实每一次我们都遍历了每一个节点,但其实只需要遍历我们需要的关键点就行了,我们看到$\sum k<=5e5$,这也就告诉了我们,如果总的时间复杂度跟$\sum k$有关的话,就可以通过,而虚树则可以帮助我们把询问点等关键点提取出来,那么总共就只需要遍历少许关键点,最后的时间复杂度就跟$\sum k$有关啦

什么是虚树?
在我的理解下,其实就是从原有的树中,把需要的关键节点和边提取出来后组成的新树,就叫虚树
虚树上的点一般有被询问的点,以及它们互相的$lca$和根节点 ,而边则存的是最小值,最大值,边权和等,视情况而定。
那么根据上面的题意,我们就可以用虚树剔除对结果无意义的点,避免过多的重复计算,从而降低时间复杂度

虚树的构建:
我们维护一个栈,维护的是一条以栈顶元素为端点的一条链(最右链)
 
首先,先把整棵树$dfs$一遍,求出每个节点的$dfs$序,顺便求出深度,举个例子,假如原树为:
 
其中黑色点为询问点,那么我们先把深度和$dfs$序求出来,如下图:
红色数字是$dfs$序,深度的话肉眼看就行了,怕图片有点混乱就省略了
一开始呢,我们求出$dfs$序后,把所有关键点按照$dfs$从小到大排序
 
具体做法(如果不懂请跟着后文图解一起思考):
假设栈顶元素为$p$,而要插入的关键节点为$x$,求出它们的$lca$,那么会有下列两种情况:
 
$1、$$lca$是$p$:如下图:
 
 
$2、$$p,x$分别位于$lca$的两棵子树上,如下图:
你可能会问, 那么为什么没有$lca$是$x$的情况呢?
 
我们思考:我们 遍历的顺序是按照$dfs$序来的,那么设对于每个点$i$的$dfs$序为$dfn[i]$,那么会有$dfn[p]<dfn[x]$,但是如果$lca$是$x$的话,$x$的$dfs$序肯定小于$p$点的,互相矛盾,所以不成立
 
那么对于上面第一种情况,我们直接将$x$点入栈即可,为什么呢?
 
我们思考$lca==p$的情况告诉了我们什么信息:
它告诉我们我们本来维护的是(下文数字都是图中$dfs$序)$1-2$这条链,而现在我们要把$4$维护进去,然而因为$lca==p$,我们发现我们可以直接维护$1-2-4$这条链,所以直接压栈即可
 
例如压栈后,栈内($dfs$序)情况如下:
就是在维护下图中红色这条链(根节点一开始就在栈内):
 
而对于第二种情况,就比较复杂了,我们设栈顶第二个元素为$q$,那么我们循环判断下面$3$种小情况讨论:
$1、$$dfn[q]>dfn[lca]$:什么意思呢?我们先画一幅图:
 
那么这幅图告诉了我们什么信息呢:
以$q$为根的子树已经遍历完毕,现在进入到了以$x$为根的子树,现在需要把以$q$为根的子树的信息存好
那么在遍历到$p$点时,我们栈中维护的是什么呢?很明显栈内元素($dfs$序)为:
 
那么也就是维护了从$1-2-3-4$这条树链,也就是说我本来维护的是下图的链:
而现在我们需要维护下图的链:
那显然我们要退栈把$p$弹出去,那么就失去了$q-p$的信息,所以我们要在虚树上连边,把要失去的信息保存下来,也就是把$q-p$在虚树上连边,退栈一次,再循环
 
所以,当$dfn[q]>dfn[lca]$时,由$q$向$p$在虚树上连边,并且退一次栈
 
$2、$$dfn[q]=dfn[lca]$:那么我们接着上幅图,退一次栈后如下图(紫色边为在虚树上已连接的边):
那么我们发现我们只需要连接$lca-p$的边就可以维护好左子树了,那么连完边后,就要维护右子树的链,所以把$x$压入栈中
所以当$dfn[q]==dfn[lca]$时,由$lca$或者$q$向$p$连边,并且把$x$节点压栈,终止循环
$3、$$dfn[q]<dfn[lca]$时:这个就比较有意思了,我们需要重新画一幅图:
 
首先,栈顶元素就是$p$,第二个元素是$q$(根节点一开始就在栈内),然而我们发现$dfn[q]<dfn[lca]$,也就是说$lca$被$p,q$夹在中间了,那么我们同样思考这代表了什么?
$1、$$p$与$lca$之间没有关键点了,因为最近的关键点为$q$,这也就告诉我们$lca$的左子树就差$lca-p$这一条边了,所以我们要再退一次栈,并且虚树上连边$lca-p$
$2、$$lca$不在栈中,也就是说$lca$不是询问点,但是它是关键点,因为它连接着$p,x$的关系等,所以它是不可忽略的,所以我们如果要维护到$x$的链的话,就必须把$lca$也加入栈中
 
所以,当$dfn[q]<dfn[lca]$时,虚树上连边$lca-p$,退栈,把$lca,x$按顺序压栈,终止循环
 
最后要注意的是,栈内还会有元素没有退出,所以最后还要把栈内元素依次连边
 
上面就是建虚树的过程了,如果还是不懂,可以配合下文一起理解,多多回味,其实很好理解的

图解虚树建立过程:
对于一开始的图,我们模拟它虚树的建立过程,初始图:
$1、$插入根节点
我们首先要维护的肯定是根节点了,那么我们把根节点入栈:
$2、$插入$3$节点
把询问点按照$dfs$序排列后,下一个待插入的点为$3$,那么 我们求出$lca(1,3)$,其实就是$1$节点,那么也就满足最上面的第一种情况$lca==p$,所以直接压栈,维护$1-3$这条链,此时栈内情况:
 
 $3、$插入$5$节点
下一个询问点为$5$节点,那么求出栈顶元素$3$与$5$的$lca$为$2$,发现,$p,x$位于不同子树,所以求出$q$为$1$,也就是下图:
我们发现$dfn[q]<dfn[lca]$,那么也就是说我们只需要连接$lca-p$就可以维护完成左子树了,(具体说明见上文),所以我们在虚树上连边$lca-p$,退栈,把$lca,x$压入栈中,那么也就是说本来维护的是$1-3$这条链,而现在改成维护$1-2-5$这条链了,那么树中栈中情况如下:
$4、$插入$8$节点
我们还是像原来一样,求出$5,8$的$lca$为$1$,那么我们求出$q$为$2$,发现$dfn[q]>dfn[lca]$也就是说$q$还在$lca$的原来遍历的子树中,那么现在$x$很明显在一个新的子树,栈内需要维护新的链,那么原来的链就要在虚树上保存下来,所以我们连接$q-p$,退栈,发现$q$变成了$1$,$p$变成了$2$,此时$dfn[q]=dfn[lca]$,那么也就是满足第二种情况,也就是告诉我们只需要连接的$lca-$这条边后这个子树就遍历完了,所以我们连边$lca-p$,退栈,把$x$压栈,维护新的链$1-8$,终止循环,完成后如下图:
$5、$插入$9$节点
我们还是按照以往一样,求出$9,8$的$lca$为$8$,也就是$lca(p,x)=p$的情况,这时候说明在一条链上,可以直接压栈维护,所以直接把$9$压栈,此时维护的链为$1-8-9$,栈内情况:
$6、$插入$10$节点
求出$10,9$的$lca$为$1$,那么发现在不同子树中,求出$q$为$8$,那么发现$dfn[q]>dfn[lca]$,那么把$q-p$连边,(为什么上文已经提及),然后退栈,此时$p=8,q=1,lca=1$,发现$dfn[q]=dfn[lca]$,所以连边$lca-p$,退栈,把$10$压栈,如下图:
$7、$清空栈内剩余元素
 
我们依次由$stack[top-1]$向$stack[top]$连边,也就是连接$1-10$,完成后如下:
那么到这里,虚树就建完了,把紫色的边提出来就是虚树了,也就是下图:

代码实现:
void Build(int x)
{
stack[++top]=1;
for(int i=0;i<mark.size();++i)
{
int x=mark[i];
int lca=Lca(x,stack[top]);
if(lca==stack[top])
{
stack[++top]=x;
return ;
}
while(top>1&&dfn[stack[top-1]]>=dfn[lca])
Add(stack[top-1],stack[top]),--top;
if(lca!=stack[top])
Add(lca,stack[top]),stack[top]=lca;
stack[++top]=x;
}
}

尾声:

如果本篇博客有问题,请联系我

如果觉得有帮助,不要吝啬你的赞$qwq$

浅谈"$fake$树"——虚树的更多相关文章

  1. 仙人掌 && 圆方树 && 虚树 总结

    仙人掌 && 圆方树 && 虚树 总结 Part1 仙人掌 定义 仙人掌是满足以下两个限制的图: 图完全联通. 不存在一条边处在两个环中. 其中第二个限制让仙人掌的题做 ...

  2. [SDOI2018]战略游戏(圆方树+虚树)

    喜闻乐见的圆方树+虚树 图上不好做,先建出圆方树. 然后答案就是没被选到的且至少有两条边可以走到被选中的点的圆点的数量. 语文不好,但结论画画图即可得出. 然后套路建出虚树. 发现在虚树上DP可以得出 ...

  3. hihoCoder #1954 : 压缩树(虚树)

    题意 有一棵 \(n\) 个节点且以 \(1\) 为根的树,把它复制成 \(m\) 个版本,有 \(q\) 次操作,每次对 \([l, r]\) 这些版本的 \(v\) 节点到根的路径收缩起来. 收缩 ...

  4. 51Nod1868 彩色树 虚树

    原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1868.html 题目传送门 - 51Nod1868 题意 给定一颗 $n$个点的树,每个点一个 $[ ...

  5. Codechef Sad Pairs——圆方树+虚树+树上差分

    SADPAIRS 删点不连通,点双,圆方树 非割点:没有影响 割点:子树DP一下 有不同颜色,所以建立虚树 在圆方树上dfs时候 如果当前点是割点 1.统计当前颜色虚树上的不连通点对,树形DP即可 2 ...

  6. BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)

    Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着 ...

  7. Luogu P4606 [SDOI2018] 战略游戏 圆方树 虚树

    https://www.luogu.org/problemnew/show/P4606 把原来的图的点双联通分量缩点(每个双联通分量建一个点,每个割点再建一个点)(用符合逻辑的方式)建一棵树(我最开始 ...

  8. BZOJ.5329.[SDOI2018]战略游戏(圆方树 虚树)

    题目链接 显然先建圆方树,方点权值为0圆点权值为1,两点间的答案就是路径权值和减去起点终点. 对于询问,显然可以建虚树.但是只需要计算两关键点间路径权值,所以不需要建出虚树.统计DFS序相邻的两关键点 ...

  9. UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)

    题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...

随机推荐

  1. PAT(B) 1093 字符串A+B(Java和C)

    题目链接:1093 字符串A+B (20 point(s)) 题目描述 给定两个字符串 A 和 B,本题要求你输出 A+B,即两个字符串的并集.要求先输出 A,再输出 B,但重复的字符必须被剔除. 输 ...

  2. 最佳thread数量

    cpu 密集性task,过多的线程反而降低了处理效率,最佳的做法就是保持和cpu core数量大致相同的线程数量: threads = number of CPUs + 1 io密集型,因为会有cpu ...

  3. YIii2.0-学习笔记之服务器安装

    最近项目需要,学习了一下yii2.0框架 第一步:  安装composer--------作为一个phper  composer不会用可能有点不合适 下载composer.phar文件 #curl - ...

  4. Java安装和环境配置

    Java安装和环境配置 从事Java开发第一关就是安装JAVA环境. 我们要安装JDK, 全称Java开发全套. 其中包含了JRE(运行时环境), 如果你打游戏的时候可能会提示你缺少JRE. 我们要做 ...

  5. HTTP响应状态码整理

    1xx:信息 100 Continue服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求.101 Switching Protocols服务器转换协议:服务器将遵从客 ...

  6. Python通用爬虫,聚焦爬虫概念理解

    通用爬虫:百度.360.搜狐.谷歌.必应....... 原理: (1)抓取网页 (2)采集数据 (3)数据处理 (4)提供检索服务 百度爬虫:Baiduspider 通用爬虫如何抓取新网站? (1)主 ...

  7. python之基础总结(飞机大战)

    一.学习python有一段时间了,总体上手还是挺好的,但是有些东西还是和Java存在着一定的区别,这里主要是通过学习,然后自己去编写一个案例.从中学习到的一些东西,这里分享出来,如果存在不正确的地方还 ...

  8. asp.net后台或前端获取TemplateField绑定的文本

    GridView中使用最多的一个是BoundField,还有一个是TemplateField 这两个各有其特点,BoundField的话比较简单,设置好DataField.HeaderText等就可以 ...

  9. LunHui 的生命观

    LunHui 的生命观 来源 https://www.zhihu.com/question/346510295 作者:齐天大圣链接:https://www.zhihu.com/question/346 ...

  10. 如何使用async和await这对组合设计统一的取Access Token的函数

    最近我在使用SAP云平台的机器学习API做和SAP系统的集成,因为SAP Cloud Platform Leonardo上的机器学期API,每次消费时需要传一个Access Token,故在每次实际调 ...