Graph 图论
次小生成树 Kruskal
首先我们要清楚的是什么是最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。所以在这之前我们需要先了解几个结论:
①如果我们要换掉其中的一条边的话新得到的生成树的权值之和一定小于等于原最小生成树的权值之和;
证明:换掉一条边之后得到的生成树的权值之和小于原最小生成树了,那么换掉之后的应该才是最小生成树,与定义不符,但是因为最小生成树不一定唯一,所以用可能等于。
②如果我们在最小生成树的基础上任意再加入一条边,会形成一个环;
证明:最小生成树的边有(V - 1)条,如果再加入一条边就有(V)条边了,(V)个顶点(V)条边一定至少会形成一个环,因为一条边连接两个点,如果有 N 条边,那么就用到了 “2N” 个点,因为我们有 N 个点,假设我们(N-1)个点只使用了一次,即所有的点都连在了一个点上,即就有(N-1)条边连在了中心那个点上,而我们又多出来了一条边,所以肯定会形成一个环;
有了上述的两个结论我们再来想次小生成树,我们先得到一个最小生成树,然后我们再往里面加入一条我们在构建这个最小生成树的时候没有用到的边,这样就形成了一个环,然后我们再把形成的这个环中最大的那条边(不是我们新加入的那条)删除掉,这样我们就得到了一个新的生成树,这个新的生成树的权值之和一定小于等于原最小生成树之和,我们只需要把剩余的边遍历一遍找出那个最小的新生成的最小生成树即可。
次小生成树的大致实现过程:
1、初始化:
对并查集进行初始化;for(int i=1;i<=n;i++){ f[i] = i;}
对vis数组初始化;
对graph数组进行初始化for(int i=1;i<=n;i++) { graph[i].clear(); graph[i].push_back(i); }
2、对边进行排序;sort(map,map+m,cmp);
3、判断当前边的两个节点是否已经属于同一集合,如果不属于:
①这条边标记为使用;
②把这两个点所在的集合统一为一个集合f[b] = a;//注意顺序!!!
③双重循环更新maxx数组maxx[i][j]:从i到j的最长边的长度;
④把b的点都加到a上注意与第二步相对应
⑤遍历边,如果这条边未被使用,假设这条边连接的是a和b,那么我们就把这条边替换掉maxx[a][b],重复此过程更新次小生成树的权值:
for(int i=0;i<m;i++){
if(vis[i]==0){
edge e = map[i];
ans = min(ans,sum-maxx[e.from][e.to]+e.cost);
}
}
完整代码如下:
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x7f7f7f7f;
int n,m;
struct edge{
int from,to;
int cost;
};
edge map[1000001];
vector<int>graph[100001];
int vis[100001];
int f[100001];
int maxx[10001][10001];
bool cmp(edge a,edge b){
return a.cost < b.cost;
}
int find(int a){
while(a!=f[a]){
a = f[a] = f[f[a]];
}
return a;
}
int kruskal(){
memset(vis,0,sizeof(vis));
int cnt = 1;
int sum = 0;
sort(map,map+m,cmp);
for(int i=1;i<=n;i++){
graph[i].clear();
graph[i].push_back(i);
f[i] = i;
}
for(int i=0;i<m&&cnt<n;i++){
edge e = map[i];
int a = e.from;
int b = e.to;
a = find(a);
b = find(b);
if(a!=b){
vis[i] = 1;
f[b] = a;//注意顺序不能错
sum += e.cost;
cnt++;
for(int j=0;j<graph[a].size();j++){
for(int k=0;k<graph[b].size();k++){
maxx[graph[a][j]][graph[b][k]] = maxx[graph[b][k]][graph[a][j]] = e.cost;
}
}
for(int j=0;j<graph[b].size();j++){
graph[a].push_back(graph[b][j]);
}
}
}
cout << sum << endl;
int ans = INF;
for(int i=0;i<m;i++){
if(vis[i]==0){
edge e = map[i];
ans = min(ans,sum-maxx[e.from][e.to]+e.cost);
}
}
return ans;
}
int main(){
cin >> n >> m;
for(int i=0;i<m;i++){
cin >> map[i].from >> map[i].to >> map[i].cost;
}
int ans = kruskal();
cout << ans << endl;
return 0;
}