一、题意
输入一个n个点m条边的图,要求你计算出图中的最小生成树和次小生成树的权值和。
二、解析
最小生成树比较简单,直接上Kruskal算法模板即可。主要是次小生成树应该如何求解?
其实也比较简单,按照以下步骤即可:
- 求出最小生成树mst
- 通过以每个点为根分别进行dfs,得到maxW[maxn][maxn]数组。其中maxW[root][v]表示最小生成树mst上从root到v的路径上的最大的一条边的权值。
- 遍历所有不在mst上的边,比如为(u, v),用它去替换mst上从u到v路径上的最大边,从而得到一系列次小生成树替补。这些次小生成树替补权值和最小的那个就是次小生成树。
时间复杂度为O(n^2+mlogm),基本够用了。
PS:原理简单,就是代码量较大...要维护一个存放边的容器E,同时还要维护一个存放最小生成树的边的容器mst。
三、代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 100 + 5, INF = 1 << 30;
struct edge {
int u, v, w;
bool flag = 0;
edge(int u, int v, int w) : u(u), v(v), w(w) {}
bool operator < (const edge& rhs) const {
return w < rhs.w;
}
};
struct mst_edge {
int v, w;
mst_edge(int v, int w) : v(v), w(w) {}
};
vector<edge> E;
vector<mst_edge> mst[maxn];
int kase, n, m, Fa[maxn], maxW[maxn][maxn], ans1, ans2;
int find(int x) {
return Fa[x] == x ? x : Fa[x] = find(Fa[x]);
}
void Kruskal() {
for(int i = 1; i <= n; i ++) Fa[i] = i;
sort(E.begin(), E.end());
ans1 = 0;
for(edge& e : E) {
int u = find(e.u), v = find(e.v);
if(u == v) continue;
Fa[u] = v;
ans1 += e.w;
e.flag = 1; // 标记这条边在mst中
mst[e.u].push_back(mst_edge(e.v, e.w)); // 将最小生成树保存起来
mst[e.v].push_back(mst_edge(e.u, e.w));
}
}
void dfs(int root, int cur, int fa, int val) { // 遍历最小生成树来生成maxW数组
maxW[root][cur] = val;
for(const mst_edge& e : mst[cur]) if(e.v != fa) {
dfs(root, e.v, cur, max(val, e.w));
}
}
void SKruskal() {
Kruskal();
for(int i = 1; i <= n; i ++) dfs(i, i, 0, 0);
ans2 = INF;
for(const edge& e : E) if(e.flag == 0) ans2 = min(ans2, ans1 - maxW[e.u][e.v] + e.w);
}
int main() {
cin >> kase;
while(kase --) {
cin >> n >> m;
E.clear();
for(int i = 1; i <= n; i ++) mst[i].clear();
while(m --) {
int u, v, w;
cin >> u >> v >> w;
E.push_back(edge(u, v, w));
}
SKruskal();
cout << ans1 << " " << ans2 << endl;
}
}四、归纳
- 次小生成树模板:
struct edge {
int u, v, w;
bool flag = 0;
edge(int u, int v, int w) : u(u), v(v), w(w) {}
bool operator < (const edge& rhs) const {
return w < rhs.w;
}
};
struct mst_edge {
int v, w;
mst_edge(int v, int w) : v(v), w(w) {}
};
vector<edge> E; // 存放所有边的容器
vector<mst_edge> mst[maxn]; // 存放最小生成树的容器
int n, m, Fa[maxn], maxW[maxn][maxn], ans1, ans2; // ans1, ans2分别为最小生成树和次小生成树的边权和
int find(int x) {
return Fa[x] == x ? x : Fa[x] = find(Fa[x]);
}
void Kruskal() {
for(int i = 1; i <= n; i ++) Fa[i] = i;
sort(E.begin(), E.end());
ans1 = 0;
for(edge& e : E) {
int u = find(e.u), v = find(e.v);
if(u == v) continue;
Fa[u] = v;
ans1 += e.w;
e.flag = 1; // 标记这条边在mst中
mst[e.u].push_back(mst_edge(e.v, e.w)); // 将最小生成树保存起来
mst[e.v].push_back(mst_edge(e.u, e.w));
}
}
void dfs(int root, int cur, int fa, int val) { // 遍历最小生成树来生成maxW数组
maxW[root][cur] = val;
for(const mst_edge& e : mst[cur]) if(e.v != fa) {
dfs(root, e.v, cur, max(val, e.w));
}
}
void SKruskal() {
Kruskal(); // 1. 求最小生成树mst
for(int i = 1; i <= n; i ++) dfs(i, i, 0, 0); // 2. 求maxW[root][v]数组
ans = INF;
for(const edge& e : E) if(e.flag == 0) ans2 = min(ans2, ans1 - maxW[e.u][e.v] + e.w);
// 3. 用不在树上的边逐个替换,然后取最小的那个树就是次小生成树
}
int main() {
......
SKruskal();
......
}- 虽然代码量大,不过只要思路清晰的话还是不容易错的

京公网安备 11010502036488号