You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
3.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 510, M = 10010;
int n, m;
// 结构体
struct Edge {
int a, b, c;
bool flag; // 是不是最小生成树中的边
bool operator<(const Edge &t) const {
return c < t.c;
}
} edge[M];
int d1[N][N]; // 从i出发到达j最短距离
int d2[N][N]; // 从i出发到达j次短距离
int sum; // 最小生成树的边权和
// 邻接表
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 并查集
int p[N];
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
/*
假设根为s,求出树中任意两点间的最长距离和严格次长距离。
需要配合换根进行枚举操作才会有效果。
s:出发点
u:到达了u点
fa:u的前序节点防止走回头路
m1:这条路径上已经获取到的最长路径
m2:这条路径上已经获取到的次长路径
*/
void dfs(int s, int u, int fa, int m1, int m2) {
for (int i = h[u]; ~i; i = ne[i]) { // 枚举u的每一条出边
int v = e[i]; // v为u的对边节点
if (v == fa) continue; // 不走回头路
int t1 = m1, t2 = m2; // 必须要复制出来td1和td2,原因是此轮要分发多个子任务此m1,m2是多个子任务共享的父亲传递过来的最大和次大值
if (w[i] > t1)
t2 = t1, t1 = w[i]; // 更新最大值、次大值
else if (w[i] < t1 && w[i] > t2)
t2 = w[i]; // 更新严格次大值
// 记录从s出发点到v节点一路上的最长路径和严格次长路径
d1[s][v] = t1, d2[s][v] = t2;
// 生命不息,探索不止
dfs(s, v, u, t1, t2);
}
}
signed main() {
cin >> n >> m;
// 初始化邻接表
memset(h, -1, sizeof h);
// Kruskal + 建图
for (int i = 0; i < m; i++)
cin >> edge[i].a >> edge[i].b >> edge[i].c;
// 按边权由小到大排序
sort(edge, edge + m);
// 初始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
// Kruskal求最小生成树
for (int i = 0; i < m; i++) {
int a = edge[i].a, b = edge[i].b, c = edge[i].c;
int pa = find(a), pb = find(b);
if (pa != pb) {
p[pa] = pb; // 并查集合并
// ①最小生成树的边权和
sum += c;
// ②最小生成树建图,无向图,为求最小生成树中任意两点间的路径中最大距离、次大距离做准备
add(a, b, c), add(b, a, c);
// ③标识此边为最小生成树中的边,后面需要枚举每条不在最小生成树中的边
edge[i].flag = 1;
}
}
// d1[i][j]和d2[i][j]
// 换根以每个点为根进行dfs,可以理解为枚举了每一种情况,肯定可以获取到任意两点间的最长路径和严格次长路径
for (int i = 1; i <= n; i++) dfs(i, i, 0, -1e18, -1e18);
int res = 1e18; // 预求最小,先设最大
// 枚举所有不在最小生成树中的边,尝试加入a->b的这条直边
for (int i = 0; i < m; i++)
if (!edge[i].flag) {
int a = edge[i].a, b = edge[i].b, c = edge[i].c;
if (c > d1[a][b]) // 最小生成树外的一条边,(a->b),如果比最小生成树中a->b的最长边长就有机会参加评选次小生成树。
// 最终的选举结果取决于它增加的长度是不是最少的
res = min(res, sum + c - d1[a][b]); // 替换最大边
else if (c > d2[a][b]) // 替换严格次大边
res = min(res, sum + c - d2[a][b]); // 严格次小生成树的边权和
}
// 输出
printf("%lld\n", res);
}