题面
Description
给你一个城市下水道网络图,你需要选出一些管道,使得在只使用这些管道的情况下,令整个网络联通,并且花费最小。
网络图可以看做是无向连通图,有 n n n个节点和 m m m条边,每条边连接 u i u_i ui和 v i v_i vi,选择的花费是 w i w_i wi。
不巧的是,由于某些原因,现在市政局要求选定某条特定的边管道,你的任务是求出对于某一条边,在选择这条管道的前提下的最小花费。
Input
第 1 1 1行包含两个整数 n n n, m m m,表示点数和边数。
第 2 2 2~ m + 1 m+1 m+1行每行三个整数 u i u_i ui, v i v_i vi, w i w_i wi,表示有一条管道连接 u i u_i ui和 v i v_i vi,费用为 w i w_i wi。
Output
输出m行,每行一个整数,表示选择第i条管道的前提下的最小花费。
管道按输入的顺序编号为 1 1 1~ m m m。
Sample Input
5 7
1 2 3
1 3 1
1 4 5
2 3 2
2 5 3
3 4 2
4 5 4
Sample Output
9
8
11
8
8
8
9
Hint
对于 20 % 20\% 20%的数据, n < = 1000 n<=1000 n<=1000, m < = 2000 m<=2000 m<=2000
对于另外 20 % 20\% 20%的数据, m < = n + 10 m<=n+10 m<=n+10
对于 100 % 100\% 100%的数据, 2 < = u i , v i < = n < = 100000 2<=u_i,v_i<=n<=100000 2<=ui,vi<=n<=100000, 1 < = m < = 200000 1<=m<=200000 1<=m<=200000, w i < = 2 31 w_i<=2^{31} wi<=231
保证初始图连通。
题解
题目就是求包含某条边的最小生成树。
先把原图的最小生成树求出来。
枚举图上的每一条边,考虑选择这条管道的前提下的最小花费:
-
如果这条边就在最小生成树上,显然,最小花费就是最小生成树的边权和。
-
如果这条边不在最小生成树上,如下图中的边 ( 5 , 6 ) (5,6) (5,6):
显然如果我们加入了边 ( 5 , 6 ) (5,6) (5,6),就会构成一个环,这个环的一部分就是 ( 5 , 6 ) (5,6) (5,6),另一部分是在树上的 5 5 5到 6 6 6的路径,即 5 ⟶ 3 ⟶ 4 ⟶ 6 5\longrightarrow3\longrightarrow4\longrightarrow6 5⟶3⟶4⟶6。
这样如果我们去掉环上的任意一条边,所有点还是联通的,且边权和比没去时更小。
又因为我们不能去掉边 ( 5 , 6 ) (5,6) (5,6),所以我们只能去掉原图的最小生成树上的 5 5 5到 6 6 6的路径的任意一边。
且为了保证去掉这条边后边权和最小,我们要去掉这条路径上边权最大的边。
这样两种情况都讨论完了,至于第二种情况的路径上边权最大值怎么维护,可以用树剖或者倍增。
代码如下:
#include<bits/stdc++.h>
#define N 1000010
#define M 2000010
#define int long long
#define ll long long
using namespace std;
struct edge
{
int u,v,w,id;
}e[M];
int n,m,fa[N];
int f[N][20],maxn[N][20],d[N];
int cnt,head[N],nxt[N<<1],to[N<<1],w[N<<1];
ll ans,Ans[N];
bool flag[N];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^'0');
ch=getchar();
}
return x*f;
}
int find(int x)
{
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
inline bool cmp(edge a,edge b)
{
return a.w<b.w;
}
inline void adde(int u,int v,int wi)
{
to[++cnt]=v;
w[cnt]=wi;
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs(int u)
{
for(int i=1;i<=18;i++)//倍增数组
{
f[u][i]=f[f[u][i-1]][i-1];
maxn[u][i]=max(maxn[u][i-1],maxn[f[u][i-1]][i-1]);
}
for(int i=head[u];i;i=nxt[i])
{
if(to[i]!=f[u][0])
{
f[to[i]][0]=u;
maxn[to[i]][0]=w[i];
d[to[i]]=d[u]+1;
dfs(to[i]);
}
}
}
inline int LCA(int a,int b)//a->lca->b
{
int maxx=0;
if(d[a]<d[b])
swap(a,b);
for(int i=18;i>=0;i--)
if(d[f[a][i]]>=d[b])
maxx=max(maxx,maxn[a][i]),a=f[a][i];
if(a==b)
return maxx;
for(int i=18;i>=0;i--)
{
if(f[a][i]!=f[b][i])
{
maxx=max(maxx,max(maxn[a][i],maxn[b][i]));
a=f[a][i],b=f[b][i];
}
}
maxx=max(maxx,max(maxn[a][0],maxn[b][0]));
return maxx;
}
signed main()
{
n=read(),m=read();
for(register int i=1;i<=n;i++)
fa[i]=i;
for(register int i=1;i<=m;i++)
{
e[i].u=read(),e[i].v=read(),e[i].w=read();
e[i].id=i;
}
sort(e+1,e+m+1,cmp);
for(register int i=1,num=0;i<=m;i++)//最小生成树
{
if(num==n-1)
break;
int a=find(e[i].u),b=find(e[i].v);
if(a!=b)
{
fa[a]=b;
adde(e[i].u,e[i].v,e[i].w);
adde(e[i].v,e[i].u,e[i].w);
ans+=e[i].w;
flag[i]=true;
num++;
}
}
d[1]=1;
dfs(1);
for(register int i=1;i<=m;i++)
{
if(flag[i])//这条边在最小生成树
{
Ans[e[i].id]=ans;
continue;
}
int maxx=LCA(e[i].u,e[i].v);//不在就求u、v路径上的边权最大值
Ans[e[i].id]=ans-maxx+e[i].w;
}
for(register int i=1;i<=m;i++)
printf("%lld\n",Ans[i]);
return 0;
}
另,相似题:XSY2485,以及题解。