“自命之神人,敢于直面第二次的,重启人生。”
Kruskal 算法
首先复习一下最小生成树的算法——$\text{Kruskal}$ ,是一种基于贪心的最小生成树算法,在最短路领域相当于 $\text{Spfa}$ ,但虽然很少被卡,总体思路在于维护一个并查集,并以权值排序枚举边,并以此加边构成一张图的最小生成树,无解情况在于 $m<n-1$ 或者图不连通,代码极其简洁:
1 | struct Edges |
这是一种普及选手都需要学习的算法,时间复杂度 $\mathcal O(m\log m)$ 。
重构树
所谓重构树,大概意思是指,对于一张图,我们重构这张图使其成为一棵二叉树,这棵二叉树有 $n$ 个叶结点,对应原来图的 $n$ 个结点。
然后这样构造出来的树并没有什么特点,因为我们并不知道如何去连接两个叶结点,合并集合。然后就随便搞一个算法给它怼上去,直到使用 $\text{Kruskal}$ 。
依然选择从小到大枚举边权,依然选择连接 $(u,v,val)$ ,如果当前 $u,v$ 未连通,则构建一个结点 $f$ ,表示为 $u,v$ 的父结点,其点权为 $val$ 。然后依次向上,会发现,因为我们按照贪心思路由小到大,那么,对于重构树上的一条路径,从根出发,向下,点权递减。
性质
很巧妙,不会证。
- $\text{Kruskal}$ 重构树有 $2n-1$ 个结点,$n$ 个叶结点。
- 如果将整棵树看作堆,则重构树必然是一个堆(取决于 $\text{Kruskal}$ 的运作方式),小根堆对应最大生成树,大根堆对应最小生成树。
- 叶结点是原图的点,非叶结点是原图的边。
- 原图中两个结点所有简单路径的边权最大值的最小值 $=$ 最小生成树中两个结点简单路径的边权最大值 $=$ 重构树中两个结点的最近公共祖先的点权。
例题
P2245 星际迷航
曾经做过,用一种更暴力的算法,求最小生成树的最大边权,则直接把树给它建出来,然后树剖线段树查询区间最大值即可,跑到 $519ms$ ,也比较快了。
然后想想怎么重构树做出来。
上面已知了,我们只需要把重构树建出来,然后在这棵二叉树上跑 $\text{Lca}$ 找到点权即可,注意到,图并非连通,所以这其实是重构森林。
有一个性质,在于,如果像动态开点线段树那样加点的话,深度越大的点编号越小,导致我们倒序枚举点编号看当前树是否已经剖分过,第一次找到的一定是当前树的根结点,比较神仙。
然后一遍板子过去即可,记得开双倍空间,跑了 $276ms$ ,次优解。
AC Code
1 | const int MAXN=2e5+10,MAXM=3e5+10; |
P1967 [NOIP2013 提高组] 货车运输
双倍经验,不过求的是最大生成树,改一下 operator
即可。
AC Code
1 | const int MAXN=2e4+10,MAXM=5e4+10; |