这是并查集的补充应用部分。
什么是生成树&&什么是最小生成树
生成树问题是指:给你一个n个点,m条边的无向带权连通图,你需要在其中找出n-1条边组成一棵树。一个图可以有很多生成树。
最小生成树:一棵边权之和最小的生成树。最小生成树可以不唯一,但最小生成树的边权和是唯一的。
最小生成树的求法
暴力
显然,我们可以暴力枚举n-1条边,完事之后判断是否是一颗树,如果是的话再统计一下边权。时间复杂度为 Cnm ,显然过大。
其中有一个明显的剪枝,就是如果加入了当前选的这条边之后,如果已经出现了环,就退出。那现在的问题就是:如何高效地判断环呢?
一个环的前身必定是一棵树,这是显然的。一个树上的点必定在一个连通块中。那么,我们只需要看一下添加一条边时这条边的两个端点在不在同一个连通块中即可。对这个操作,你当然可以用LCT维护动态并查集,或者可持久化一下,我们只能用DFS。
贪心
经过前人伟大的YY,我们得出了一个结论:
如果我们按从小到大的顺序枚举边,则得到的就是最小生成树。算法流程如下:
- 把所有的边权排序
重复以下操作,直到已经加入n-1条边
- 选取一条权值最小的未判断过的边。
- 如果这条边的两个端点不在同一个连通块中,则加入这条边,并用并查集将两个端点合并。否则不做任何事。
代码实现(伪代码)如下:
const int maxn=...;
const int maxm=...;
struct edge{
int s,t,len;
};
edge E[maxm];
int fa[maxn];
inline int ask(int x){
return fa[x]==x?x:fa[x]=ask(fa[x]);
}//并查集实现
bool cmp(edge a,edge b){
return a.len<b.len;
}
int main(){
//n是点数,m是边数
init();//读入
sort(E+1,E+m+1,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++){//可以直接从1-m循环
int x=ask(E[i].s),y=ask(E[i].t);
if (x!=y){
fa[x]=y;
ans+=E[i].len;
}
}
cout<<ans;
return 0;
}