洛谷 P14080 [GESP202509 八级] 最小生成树
题目背景
对应的选择、判断题:https://ti.luogu.com.cn/problemset/1196
题目描述
给定一张包含 \(n\) 个结点 \(m\) 条边的带权连通无向图,结点依次以 \(1,2,…,n\) 编号,第 \(i\) 条边\((1≤i≤m)\)连接结点 \(u_i\) 与结点 \(v_i\),边权为 \(w_i\)。
对于每条边,请你求出从图中移除该条边后,图的最小生成树中所有边的边权和。特别地,若移除某条边后图的最小生成树不存在,则输出 \(−1\)。
请注意,数据可能存在重边。
输入格式
第一行,两个正整数 \(n,m\),分别表示图的结点数与边数。
接下来 \(m\) 行中的第 \(i\) 行\((1≤i≤m)\)包含三个正整数 \(u_i,v_i,w_i\),表示图中连接结点 \(u_i\) 与结点 \(v_i\) 的边,边权为 \(w_i\)。
输出格式
输出共 \(m\) 行,第 \(i\) 行\((1≤i≤m)\)包含一个整数,表示移除第 \(i\) 条边后,图的最小生成树中所有边的边权和。若移除第 \(i\) 条边后图的最小生成树不存在,则输出 \(−1\)。
输入输出样例
输入 #1复制运行
5 5
1 2 4
2 3 3
3 4 1
2 5 2
3 1 8
输出 #1复制运行
14
15
-1
-1
10
输入 #2复制运行
6 10
1 2 6
2 3 3
3 1 4
3 4 5
4 5 8
5 6 2
6 4 1
3 2 4
5 4 4
3 3 6
输出 #2复制运行
15
16
17
-1
15
17
18
15
15
15
说明/提示
| 子任务编号 | 测试点占比 | n | m | 特殊性质 |
|---|---|---|---|---|
| \(1\) | \(20\%\) | \(\le 50\) | \(\le 100\) | - |
| \(2\) | \(30\%\) | \(\le 10^5\) | \(\le 10^5\) | \(n=m\) |
| \(3\) | \(30\%\) | \(\le 500\) | \(\le 2×10^4\) | - |
| \(4\) | \(20\%\) | \(\le 10^5\) | \(\le 10^5\) | - |
对于所有测试点,保证 \(1 \le n \le 10^5,1 \le m \le 10^5,1 \le u_i,v_i \le n,1 \le w_i \le 10^9\)。
算法分析
首先暴力的时间复杂度是\(O(nm)\),会超时,不行。正解是先求出原图的最小生成树,把这些边记录下来,然后\(O(m)\)枚举每一条要删的边,如果是最小生成树外的边,不影响答案,直接输出最小生成树的权值和。如果是最小生成树里的边,那该怎么处理呢?观察发现,每一条树内的边要被删掉的话,都会有一条树外的边来替代它,具体看下图:

红边是最小生成树的边,而黄边则是要替代的边,发现可以被删掉的边就是这条边的两个顶点的唯一简单路径上经过的边,所以可以用\(lca\)来解决,这题就可以了。
AC代码
#include <bits/stdc++.h>
using namespace std;
struct node{
int x,y,z,id;
bool operator<(node a) const {
return z<a.z;
}
};
int n,m;
node a[100005];
int fa[100005];
int dep[100005];
int id[100005];
int nxt[100005];
int f[100005][25];
long long sum = 0;
long long ans[100005];
bool vis[100005];
vector<node> b[100005];
int find(int x){
if(fa[x]==x){
return x;
}else{
return fa[x] = find(fa[x]);
}
}
void kruskal(){
sort(a+1,a+1+m);
for(int i = 1; i<=m; i++){
if(find(a[i].x)!=find(a[i].y)){
vis[a[i].id] = 1;
sum+=a[i].z;
fa[find(a[i].x)] = a[i].y;
}
}
}
void dfs(int x,int father){
dep[x] = dep[father]+1;
nxt[x] = father;
f[x][0] = father;
for(int i = 1; i<=19; i++){
f[x][i] = f[f[x][i-1]][i-1];
}
for(node i : b[x]){
if(!vis[i.id] || i.y==father){
continue;
}
id[i.y] = i.id;
dfs(i.y,x);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
for(int i = 19; i>=0; i--){
if(dep[f[x][i]]>=dep[y]){
x = f[x][i];
}
}
if(x==y){
return y;
}
for(int i = 19; i>=0; i--){
if(f[x][i]!=f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
void js(int x,int f,int z){
while(dep[x]>dep[f]){
if(!ans[id[x]]){
ans[id[x]] = z;
}
int& t = nxt[x];
x = t;
t = f;
}
}
int main(){
cin>>n>>m;
for(int i = 1; i<=n; i++){
fa[i] = i;
}
for(int i = 1; i<=m; i++){
cin>>a[i].x>>a[i].y>>a[i].z;
a[i].id = i;
b[a[i].x].push_back(node{a[i].x,a[i].y,a[i].z,a[i].id});
b[a[i].y].push_back(node{a[i].y,a[i].x,a[i].z,a[i].id});
}
kruskal();
dfs(1,1);
for(int i = 1; i<=m; i++){
if(!vis[a[i].id]){
int t = lca(a[i].x,a[i].y);
js(a[i].x,t,a[i].z);
js(a[i].y,t,a[i].z);
}
}
for(int i = 1; i<=m; i++){
if(vis[a[i].id]){
if(ans[a[i].id]){
ans[a[i].id]+=sum-a[i].z;
}else{
ans[a[i].id] = -1;
}
}else{
ans[a[i].id] = sum;
}
}
for(int i = 1; i<=m; i++){
printf("%lld\n",ans[i]);
}
return 0;
}
总结
这题的思路并不难想,但代码很难调,我就调了两个多小时才调出来,不愧是青题啊。

浙公网安备 33010602011771号