|
|
#include <bits/stdc++.h>
|
|
|
using namespace std;
|
|
|
typedef long long LL;
|
|
|
const int N = 100010, M = 300010;
|
|
|
const int INF = 0x3f3f3f3f;
|
|
|
typedef pair<int, int> PII;
|
|
|
int f[N][16]; // f[i][k]表示树上的某节点i向上走2^k步到达的节点
|
|
|
PII d[N][16]; // d[i][k]表示树上的某节点i向上走2^k步到达的节点最长距离和次长距离
|
|
|
int depth[N]; // 深度数组
|
|
|
|
|
|
// Kruskal用的结构体
|
|
|
struct Edge {
|
|
|
int a, b, c; // 从a到b边权为c
|
|
|
bool flag; // 是不是最小生成树的树边
|
|
|
const bool operator<(const Edge &ed) const {
|
|
|
return c < ed.c;
|
|
|
}
|
|
|
} edge[M];
|
|
|
|
|
|
// 邻接表
|
|
|
int e[M], h[N], idx, w[M], ne[M];
|
|
|
void add(int a, int b, int c) {
|
|
|
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
|
|
|
}
|
|
|
// 并查集
|
|
|
int p[N];
|
|
|
int find(int x) {
|
|
|
if (x == p[x]) return x;
|
|
|
return p[x] = find(p[x]);
|
|
|
}
|
|
|
|
|
|
// 树上倍增求任意两点最短距离
|
|
|
int bfs(int root) {
|
|
|
queue<int> q;
|
|
|
q.push(root);
|
|
|
depth[root] = 1;
|
|
|
while (q.size()) {
|
|
|
int u = q.front();
|
|
|
q.pop();
|
|
|
for (int i = h[u]; ~i; i = ne[i]) {
|
|
|
int j = e[i];
|
|
|
if (!depth[j]) {
|
|
|
q.push(j);
|
|
|
depth[j] = depth[u] + 1; // 记录深度
|
|
|
|
|
|
f[j][0] = u; // 记录2^0->t,描述父节点
|
|
|
|
|
|
// 下面是与普通的倍增不一样的代码,记录两点间最大长度与次大长度
|
|
|
d[j][0] = {w[i], 0}; // j->t 的最大距离与次大距离
|
|
|
|
|
|
for (int k = 1; k <= 15; k++) { // 倍增
|
|
|
int v = f[j][k - 1]; // 设j跳2 ^(k-1)到达的是v点
|
|
|
f[j][k] = f[v][k - 1]; // v点跳 2^(k-1)到达的终点就是j跳2^k的终点
|
|
|
|
|
|
// ①最大边权一定是两个线段中最长边权的最大值
|
|
|
d[j][k].first = max(d[j][k - 1].first, d[v][k - 1].first);
|
|
|
|
|
|
// ②次大边权分情况讨论
|
|
|
// 如果前半段的最大值 小于 后半段的最大值
|
|
|
// 次大值 等于 max(前半段最大值,后半段次大值)
|
|
|
if (d[j][k - 1].first == d[v][k - 1].first)
|
|
|
// 次大值 等于 max(前半段次大值,后半段次大值)
|
|
|
d[j][k].second = max(d[j][k - 1].second, d[v][k - 1].second);
|
|
|
// 如果前半段的最大值 等于 后半段的最大值
|
|
|
else if (d[j][k - 1].first < d[v][k - 1].first)
|
|
|
d[j][k].second = max(d[j][k - 1].first, d[v][k - 1].second);
|
|
|
// 如果前半段的最大值 大于 后半段的最大值
|
|
|
else // 次大值 等于 max(前半段次大值,后半段的最大值)
|
|
|
d[j][k].second = max(d[j][k - 1].second, d[v][k - 1].first);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 因为同时需要同步修改最大值和次大值,所以采用了地址符&引用方式定义参数
|
|
|
// m1:最大值,m2:次大值
|
|
|
void cmp(int &m1, int &m2, PII x) {
|
|
|
if (m1 == x.first)
|
|
|
m2 = max(m2, x.second);
|
|
|
else if (m1 < x.first)
|
|
|
m2 = max(m1, x.second), m1 = x.first;
|
|
|
else
|
|
|
m2 = max(m2, x.first);
|
|
|
}
|
|
|
// 最近公共祖先
|
|
|
// 由a->b的边,边权是w
|
|
|
// 返回值:如果加上这条边w,去掉最小生成树中的某条边(m1或m2),得到一个待选的次小生成树
|
|
|
// 此时的 w- m1 或者 w-m2的值是多少。
|
|
|
// 具体是-m1,还是-m2,要区别对待,因为如果w=m1,就是-m2,否则就是-m1
|
|
|
// 利用倍增的思想,对bfs已经打好的表 d数组和f数组 进行快速查询
|
|
|
// 找出a->b之间的最大距离和次大距离
|
|
|
int lca(int a, int b, int w) {
|
|
|
if (depth[a] < depth[b]) swap(a, b); // 保证a的深度大于b的深度
|
|
|
int m1 = -1e18, m2 = -1e18; // 最大边,次大边初始化
|
|
|
for (int k = 15; k >= 0; k--) // 由小到大尝试
|
|
|
if (depth[f[a][k]] >= depth[b]) { // 让a向上跳2^k步
|
|
|
cmp(m1, m2, d[a][k]); // a向上跳2^k步时,走过的路径中可能存在最大边或次大边
|
|
|
a = f[a][k]; // 标准的lca
|
|
|
}
|
|
|
|
|
|
// 当a与b不是同一个点时,此时两者必须是depth一样的情况,同时向上查询2^k,必然可以找到LCA
|
|
|
if (a != b) {
|
|
|
for (int k = 15; k >= 0; k--)
|
|
|
if (f[a][k] != f[b][k]) {
|
|
|
cmp(m1, m2, d[a][k]); // a向上跳2^k步时,走过的路径中可能存在最大边或次大边
|
|
|
cmp(m1, m2, d[b][k]); // b向上跳2^k步时,走过的路径中可能存在最大边或次大边
|
|
|
a = f[a][k], b = f[b][k];
|
|
|
}
|
|
|
// 此时a和b到lca下同一层 所以还要各跳1步=跳2^0步
|
|
|
// 联想一下在普通版本LCA中的最终返回值就明白了
|
|
|
// return f[a][0];
|
|
|
cmp(m1, m2, d[a][0]);
|
|
|
cmp(m1, m2, d[b][0]);
|
|
|
}
|
|
|
return w == m1 ? w - m2 : w - m1;
|
|
|
}
|
|
|
int main() {
|
|
|
int n, m, a, b, c;
|
|
|
scanf("%d %d", &n, &m);
|
|
|
|
|
|
// 并查集初始化
|
|
|
for (int i = 1; i <= n; i++) p[i] = i;
|
|
|
// 邻接表初始化
|
|
|
memset(h, -1, sizeof h);
|
|
|
|
|
|
// Kruskal
|
|
|
for (int i = 0; i < m; i++) {
|
|
|
scanf("%d %d %d", &a, &b, &c);
|
|
|
edge[i] = {a, b, c, false};
|
|
|
}
|
|
|
|
|
|
// 按边权排序+最小生成树
|
|
|
sort(edge, edge + m);
|
|
|
|
|
|
LL sum = 0, ans = 1e18;
|
|
|
|
|
|
for (int i = 0; i < m; i++) {
|
|
|
a = find(edge[i].a), b = find(edge[i].b), c = edge[i].c;
|
|
|
if (a != b) {
|
|
|
p[a] = b;
|
|
|
|
|
|
sum += c; // 最小生成树的边权总和
|
|
|
edge[i].flag = true; // 标识为最小生成树中的边
|
|
|
// 将最小生成树中的树边单独构建一个图出来
|
|
|
add(edge[i].a, edge[i].b, c), add(edge[i].b, edge[i].a, c);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 倍增预处理,记录任意点向上2^k步的最大值,次大值,深度等信息,后面lca会用到
|
|
|
bfs(1);
|
|
|
|
|
|
// 用非树边去尝试替换最小生成树中的边,然后取min
|
|
|
// lca查表
|
|
|
for (int i = 0; i < m; i++)
|
|
|
if (!edge[i].flag) { // 枚举非树边
|
|
|
a = edge[i].a, b = edge[i].b, c = edge[i].c;
|
|
|
ans = min(ans, sum + lca(a, b, c));
|
|
|
}
|
|
|
// 输出
|
|
|
printf("%lld\n", ans);
|
|
|
return 0;
|
|
|
} |