题目大意
给定n个点m条边的森林,每条边有边权。要求用长度为L的边把它连成一棵树,且直径最小。
n≤500000
分析
首先对于森林中每一棵树,它只有一条路径可能会对答案有贡献。为了使答案尽量小,那就要使这个值尽量小。那么可以选择它的所有点中,到达其它点距离最大值最小的,去和其它树连接。然后这个点对答案的贡献就是这个距离。
那么会发现,这些数看成一个点之后,又会连成一棵树。
由于这棵树的形态是任意的,肯定是选择一个点作为根,然后其它点和根直接连接最优。为了答案尽量小,又一定是把上面所述距离的最大值作为根。
那么对答案有贡献的就只有三种情况:
1. 原来每棵树的直径
2. 距离最大值+距离次大值+L
3. 距离次大值+距离第3大值+2L
复杂度是线性的。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=500005,M=1000005;
typedef long long LL;
int n,m,L,tot,h[N],e[M],nxt[M],w[M],fa[N],dis[N],D[N],ans,cnt,len[N];
bool v[N];
char c;
int read()
{
for (c=getchar();c<'0' || c>'9';c=getchar());
int x=c-48;
for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
return x;
}
void add(int x,int y,int d)
{
e[++tot]=y; nxt[tot]=h[x]; w[tot]=d; h[x]=tot;
}
int main()
{
n=read(); m=read(); L=read();
while (m--)
{
int x=read()+1,y=read()+1,w=read();
add(x,y,w); add(y,x,w);
}
for (int i=1;i<=n;i++) if (!v[i])
{
D[tot=1]=i;
for (int j=1;j<=tot;j++)
{
int x=D[j];
v[x]=1;
for (int k=h[x];k;k=nxt[k]) if (e[k]!=fa[x])
{
D[++tot]=e[k]; dis[e[k]]=dis[x]+w[k]; fa[e[k]]=x;
}
}
int r=D[tot];
for (int j=1;j<tot;j++) if (dis[D[j]]>dis[r]) r=D[j];
D[tot=1]=r; dis[r]=0; fa[r]=0;
for (int j=1;j<=tot;j++)
{
int x=D[j];
for (int k=h[x];k;k=nxt[k]) if (e[k]!=fa[x])
{
D[++tot]=e[k]; dis[e[k]]=dis[x]+w[k]; fa[e[k]]=x;
}
}
r=D[tot];
for (int j=1;j<tot;j++) if (dis[D[j]]>dis[r]) r=D[j];
ans=max(ans,dis[r]); len[++cnt]=dis[r];
for (int j=r;j>0;j=fa[j]) len[cnt]=min(len[cnt],max(dis[j],dis[r]-dis[j]));
}
sort(len+1,len+cnt+1);
if (cnt==2) ans=max(ans,len[2]+len[1]+L);
else if (cnt>2) ans=max(ans,max(len[cnt]+len[cnt-1]+L,len[cnt-1]+len[cnt-2]+L*2));
printf("%d\n",ans);
return 0;
}