文章目录
Kruskal算法
众所周知,Kruskal是一种优秀的基于并查集的最小(最大)生成树算法。它的简要流程为:
- 将所有边按边权排序;
- 依次枚举所有边的两端点 u u u和 v v v,若它们不在一个联通块内,则将该边加入生成树的边集中,且将 u u u、 v v v合并于一个连通块中。
Kruskal重构树
定义
Kruskal重构树的定义(构造方式):
- 一开始,各个结点都在一个以自己为根的连通块中;
- 执行Kruskal算法,重复以下步骤:
- 把即将加入生成树边集的边,看做一个新点 x x x, x x x的两儿子分别是 u u u所在的连通块对应的根和 v v v所在的连通块对应的根, x x x的点权是该边的边权;
- 合并 u u u、 v v v,这个合并后的连通块的根是 x x x。
举个建树的例子(数据来自于BZOJ3545 [ONTAK2010]Peaks):
注意,最开始的叶子结点的顺序不重要,这里为了好看,重新排列了一下。
性质
于是Kruskal重构树的定义就弄清楚了。
下面这个不是动图:
接下来看一下这个东西的性质(接下来只探讨最小生成树):
- Kruskal重构树是一个二叉树;
废话。
- Kruskal重构树的叶子没有点权,其他点都有点权;
废话。
- Kruskal重构树是完全二叉树(除叶结点外,每个结点都有两个儿子);
废话。
- 原图中的两点 u u u、 v v v在Kruskal重构树上的LCA的点权是原图中 u u u到 v v v的所有路径中最大边的最小值。
先考虑:由于是按边权从小到大插入的,所以某个非根非叶子结点的点权一定比它的父亲的点权小;
所以, u u u和 v v v在Kruskal重构树上的LCA的点权,就是在最小生成树上的 u → v u\to v u→v的路径(由于是树,所以是唯一路径)中的最长边;
又因为是最小生成树,所以满足这个最长边最小。
例题
两道板子题
A NOIP2013货车运输
B BZOJ3732 Network
这两道题利用上面的最后一个性质,输出LCA的点权即可。
当然,这两道题也可以直接用最小(最大)生成树来做,用一个倍增数组记录路径上边权的最大值(最小值)就行了。
下面是A题的两种方法的代码:
- Kruskal重构树(这道题可能不连通= =,详见下面注释的部分)
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9') f|=c=='-',c=getchar();
while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
return f?-x:x;