https://www.luogu.com.cn/problem/P4180
这篇文章将会默认读者已经掌握了LCA,kruskal等相关基础知识点
- 考虑最小生成树的 k r u s k a l kruskal kruskal算法,我们构造好一颗最小生成树以后,如何找到次小?容易想到,我们依然按照贪心策略,再加入一条边权最小的边,那么此时一定出现了环,那么我们把这个环上除了刚才加进来的这条边以外最大的那条边删除掉,得到的就是次小生成树。
- 如何证明,考虑这样几条边 a , b , c , d , ( a < b < c < d ) a,b,c,d,(a<b<c<d) a,b,c,d,(a<b<c<d),假设现在 a , b a,b a,b已经在最小生成树里面,那么我们现在要把 c c c加进去,肯定是把 b b b换下来最优;如果这样不最优,我加个 d d d,那也得把 b b b换下来,那还不如刚才的情况,所以这样可以得到次小
- 那怎么保证严格次小呢?只需要维护一个最大和次大就好了;考虑为什么会不严格,就是因为换下来的边有可能和加进来的那条边长度相等,所以如果发现相等,只要知道次小边是谁直接换这个就好了;如果不相等就换最大边
- 这里不画图应该也容易理解
那么现在考虑如何实现,怎么高效的维护最大和次大呢?如果暴力每次都是 O ( n ) O(n) O(n)的,肯定不行,我要考虑一个 O ( 1 ) O(1) O(1)的方法,那就是倍增,预处理出来所有节点之间的父子关系,从哪出发都行,根据 S T ST ST表或者 L C A LCA LCA思想,同时求整棵树上的父子关系、边权最大、次大倍增数组,共计三个数组,这样信息预处理就解决了
- 建立最小生成树,同时标记已经用过的边
- 从任意一个节点开始 D f s Dfs Dfs,统计节点深度,以及三个倍增数组,分别为 f [ i ] [ j ] , g [ i ] [ j ] , h [ i ] [ j ] f[i][j],g[i][j],h[i][j] f[i][j],g[i][j],h[i][j]表示父子关系、最大、次大,其中 i i i表示节点编号, j j j表示父子之间的传递代数(传宗接代的代,懂的都懂)
- 开始加边,设端点为 u , v u,v u,v,求出 u u u和 v v v的 L C A LCA LCA,求 u → L C A u\rightarrow LCA u→LCA和 v → L C A v\rightarrow LCA v→LCA这两条链上的可以替换的边,求形成的生成树的边权和,取最小值,一直计算到结束
把DFS的程序单独拿出来解释一下,其余部分都比较常规
void Dfs(int u, int fa, ll w){
dep[u] = dep[fa] + 1;
f[u][0] = fa;
g[u][0] = w;
h[u][0] = -INF;
for(int i=1;i<=20;i++){
f[u][i] = f[f[u][i - 1]][i - 1];
g[u][i] = max(g[u][i - 1], g[f[u][i - 1]][i - 1]);
h[u][i] = max(h[u][i - 1], h[f[u][i - 1]][i - 1]);
if(g[u][i - 1] > g[f[u][i - 1]][i - 1]){
h[u][i] = max(h[u][i], g[f[u][i - 1]][i - 1]);
}else if(g[u][i - 1] < g[f[u][i - 1]][i - 1]){
h[u][i] = max(h[u][i], g[u][i - 1]);
}
}
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
Dfs(v, u, edge[i].val);
}
}
- 首先统计深度,然后我设置了 20 20 20代,大概是 2 20 = 1024 × 1024 2^{20}=1024\times1024 220=1024×1024,范围足够,重点关注如何求取 g g g和 h h h两个倍增数组
- 首先倍增不能少,也就是 g [ u ] [ i ] = m a x ( g [ u ] [ i − 1 ] , g [ f [ u ] [ i − 1 ] ] [ i − 1 ] ) g[u][i]=max(g[u][i-1],g[f[u][i-1]][i-1]) g[u][i]=max(g[u][i−1],g[f[u][i−1]][i−1])这个递推过程就是 u u u往上第 2 i 2^i 2i代的求取过程,显然就是 2 i − 1 2^{i-1} 2i−1代往上 2 i − 1 2^{i-1} 2i−1代,接下来的判断就是在找次大,因为最大没什么好说的就一直 m a x max max就好了,次大的意思是严格小于最大的最大数,所以这两部分我要比较一下哪部分更大,如果一样大那就没有,所以题目保证一定存在严格次小生成树,不用担心;否则取较小的一部分作为次大,也就是 u u u到它爷爷的次大应该在 u u u到他爸爸的最大和 u u u爸爸到 u u u爷爷的最大里面选择一个相对较小的,这样应该就可以理解这个过程了
- 完整程序如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 100;
const ll INF = 1e16;
struct st{
int u, v;
ll w;
bool operator < (const st &B)const{
return w < B.w;
}
}s[MAXN];
struct Edge{
int next;
int to;
ll val;
}edge[MAXN];
int head[MAXN];
int cnt;
void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
int f[MAXN][30];
ll g[MAXN][30];
ll h[MAXN][30];
int dep[MAXN];
int LCA(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
for(int i=20;i>=0;i--){
if(dep[f[u][i]] >= dep[v]){
u = f[u][i];
}
}
if(u == v) return u;
for(int i=20;i>=0;i--){
if(f[u][i] != f[v][i]){
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
int fa[MAXN];
int FIND(int x){
return x == fa[x] ? x : fa[x] = FIND(fa[x]);
}
int vis[MAXN];
ll Kruskal(int n, int m){
ll sum = 0;
int num = 0;
for(int i=1;i<=m;i++){
int u = FIND(s[i].u);
int v = FIND(s[i].v);
ll w = s[i].w;
if(u == v) continue;
Add_Edge(s[i].u, s[i].v, w);
Add_Edge(s[i].v, s[i].u, w);
vis[i] = 1;
fa[u] = v;
num += 1;
sum += w;
if(num == n - 1) break;
}
return sum;
}
void Dfs(int u, int fa, ll w){
dep[u] = dep[fa] + 1;
f[u][0] = fa;
g[u][0] = w;
h[u][0] = -INF;
for(int i=1;i<=20;i++){
f[u][i] = f[f[u][i - 1]][i - 1];
g[u][i] = max(g[u][i - 1], g[f[u][i - 1]][i - 1]);
h[u][i] = max(h[u][i - 1], h[f[u][i - 1]][i - 1]);
if(g[u][i - 1] > g[f[u][i - 1]][i - 1]){
h[u][i] = max(h[u][i], g[f[u][i - 1]][i - 1]);
}else if(g[u][i - 1] < g[f[u][i - 1]][i - 1]){
h[u][i] = max(h[u][i], g[u][i - 1]);
}
}
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
Dfs(v, u, edge[i].val);
}
}
ll Get(int u, int v, ll mx){
ll ans = -INF;
for(int i=20;i>=0;i--){
if(dep[f[u][i]] >= dep[v]){
if(mx != g[u][i]){
ans = max(ans, g[u][i]);
}else{
ans = max(ans, h[u][i]);
}
u = f[u][i];
}
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
memset(head, -1, sizeof head);
cin >> n >> m;
for(int i=1;i<=n;i++) fa[i] = i;
for(int i=1;i<=m;i++){
int u, v;
ll w;
cin >> u >> v >> w;
s[i].u = u;
s[i].v = v;
s[i].w = w;
}sort(s + 1, s + 1 + m);
ll sum = Kruskal(n, m);
Dfs(2, 0, 0);
ll ans = INF;
for(int i=1;i<=m;i++){
if(!vis[i]){
int u = s[i].u;
int v = s[i].v;
int lca = LCA(u, v);
ll s1 = Get(u, lca, s[i].w);
ll s2 = Get(v, lca, s[i].w);
// cout << s1 << ' ' << s2 << '\n';
ans = min(ans, sum - max(s1, s2) + s[i].w);
}
}
cout << ans;
return 0;
}