浅谈kruskal重构树
Ps:前置知识:基本图论、最小生成树。
kruskal求最小生成树
相信大家对这玩意已经很熟悉了,不过在这里我还是略微提一下。
前置知识:并查集、贪心
kruskal是一种求最小生成树的算法,具体操作:
- 将所有边按边权从小到大排序。
- 选取边权最小的一条边。
- 如果所选的边的两个顶点不属于同一个最小生成树的点集里,将这条边加入边集里,两个点并入点集里;否则继续执行二操作,直至所有点都进入同一个集合里。
- 将最小生成树上的边权加起来,就可以求得最小生成树的权值。
图例
这里给出核心代码:
int kruskal(){
sort(e,e+1+m,cmp);
int ans=0,cnt=0;
for(int i=1;i<=m;i++){
int fu=find(e[i].u),fv=find(e[i].v);
if(fu==fv)continue;
fa[fu]=fv;
ans+=e[i].w;
cnt++;
}
if(cnt<n-1)return -1;
else return ans;
}
证明
刚才提到,kruskal的算法大致就是按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了
现在我们要证明的是:在任何时候,kruskal算法选的边都被最小生成树的边集所包含。
很容易想到可以用数学归纳法证明,具体证明如下;
对于算法刚开始时,最小生成树存在,命题成立。
我们可以假设当前边集为
考虑这个环上不属于
首先可以确定的是
其次
综上,显然
所以,
正文
好的,前置知识已经全部讲完了,接下来切入正题:
kruskal重构树
给定
首先我们考虑暴力,以
我们再考虑用最小生成树优化,很显然最长边最小的那条路径的边集属于整张图最小生成树的边集(听起来有些拗口,大家请见谅),于是我们很容易想到求出最小生成树,然后暴力求两点之间最小生成树上的最长边,复杂度
正解
先看一张图:(图片来源于CSDN博客-------Gragh Theory--------,在此深表感谢)
上面这张图清楚地表示出了kruskal重构树的建树过程,具体它是这样构造的:(可以结合图来看):
- 将边权从小到大排个序(不用我说)
- 选取当前边权最小的一条边,令两个顶点为
u,v ,如果u,v 不在同一个并查集里,那么将其合并,并且新建一个节点p ,将其点权设为u→v 这条边的边权,重复执行此操作直至所有点都加入同一个并查集。
很显然,kruskal重构树满足这样三个性质:
- 它是个二叉树
-
- 满足大根堆的性质(即满足任一节点满足其点权大于左儿子和右儿子)
所以现在我们只需要先建树,然后再求
贴上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1000;
const int M=N*2;
const int INF=1e9+7;
int cnt;
int n,m,k;
struct edge{//构建边结构体
int u,v,w;
bool operator <(const edge &a)const{//重载运算符
return w<a.w;
}
}e[M];
int val[M];
int f[N][25];
int ch[N][2];
int sum;
void add_edge(int u,int v,int w){//加边函数
e[++sum]={u,v,w};
}
int fa[N];
int find(int x){//并查集find函数
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
int dep[N];
void dfs(int x){//递归搜索
if(!ch[x][0]&&!ch[x][1])return;
dep[ch[x][0]]=dep[ch[x][1]]=dep[x]+1;
dfs(ch[x][0]);
dfs(ch[x][1]);
}
int LCA(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int main(){
cin>>n>>m>>k;
cnt=n;
int u,v,w;
sum=0;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add_edge(u,v,w);
}
for(int i=1;i<=n*2;i++){
fa[i]=i;
}
sort(e+1,e+1+m);
for(int i=1;i<=m;i++){
int fu=find(e[i].u);
int fv=find(e[i].v);
if(fu!=fv){
cnt++;
ch[cnt][0]=fu;
ch[cnt][1]=fv;
fa[fa[e[i].u]]=fa[fa[e[i].v]]=f[fa[e[i].u]][0]=f[fa[e[i].v]][0]=cnt;
val[cnt]=e[i].w;
}
}
dep[cnt]=1;
dfs(cnt);
for(int i=1;i<=20;i++){
for(int j=1;j<=2*n;j++){
f[j][i]=f[f[j][i-1]][i-1];
}
}
int x,y;
while(k--){
cin>>x>>y;
cout<<val[LCA(x,y)]<<endl;
}
return 0;
}
/*
input:
5 6 2
1 4 2
1 5 1
4 5 2
2 4 1
2 3 4
3 5 3
2 3
1 2
output:
3
2
*/
kruskal重构树到这里就讲完了,放两道例题:
P1967货车运输
P7834 Peaks 加强版
总结
其实图论有些知识还是很精妙的,希望OIer们能多学习学习图论相关的知识,最后祝大家的OI生涯一帆风顺!
小广告
OI-Wiki是个好东西