题意
给定一个 n n 个点,条边的连通图,求直径(最远点对距离)
题解
如果是树,那么大家都晓得答案肯定是直径的一半,其实这个结论推广到本题也是正确的(有人会证吗?我不会……)
考虑当 n≤2000 n ≤ 2000 时的60分做法:找到环之后,枚举断掉环上的哪一条边,然后求树的直径,取 min m i n 即可,这样做是平方的复杂度
想想怎么优化,现在瓶颈就在于枚举之后的求直径是 O(n) O ( n ) 的,如果我们能够把这个过程优化到 O(1) O ( 1 ) ,那么问题就可以线性解决
我们把环提到上面来,把树的其他部分挂在环上面(环基树),破环为链。然后我们记: sum1[i],sum2[i],Sum1[i],Sum2[i] s u m 1 [ i ] , s u m 2 [ i ] , S u m 1 [ i ] , S u m 2 [ i ] 分别表示链的前缀长度+i的子树深度的最大值;前缀链中两个点的子树深度之和+两点距离的最大值;后缀中……;后缀中……。那么如果断开环上的第 i i 个点与第个点之间的边,能够产生的直径就是 max(max(sum2[i],Sum2[i+1]),sum1[i]+Sum1[i]+L) m a x ( m a x ( s u m 2 [ i ] , S u m 2 [ i + 1 ] ) , s u m 1 [ i ] + S u m 1 [ i ] + L ) , L L 为第1个点与环上最后一个点之间的边长(即破环为链时断开的那条边,特别特别要注意需要考虑这一条边,否则在UOJ上会被hack)
复杂度
O()
代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
#define Rint register int
#define Lint long long int
using namespace std;
const int N=100010;
struct node
{
int next,to,w;
}t[N*2];
bool vis[N];
int head[N],num;
int q[N],w[N],dfn[N],low[N],sta[N];
int n,top,cnt,len;
Lint dep[N],dis[N],sum1[N],sum2[N],Sum1[N],Sum2[N];
Lint ans,Ans=1e18;
void add(int u,int v,int w)
{
t[++num]=(node){ head[u],v,w };
head[u]=num;
}
void Tarjan(Rint k,Rint fa)
{
dfn[k]=low[k]=++cnt;
sta[++top]=k;
for(int i=head[k],x; i ;i=t[i].next)
{
x=t[i].to;
if( !dfn[x] )
{
Tarjan( x,k );
low[k]=min( low[k],low[x] );
}
else if( x!=fa ) low[k]=min( low[k],dfn[x] );
}
if( dfn[k]==low[k] )
{
int x=sta[top];
if( x==k ) { top--;return ; }
while( x!=k )
{
q[++len]=x,vis[x]=1;
x=sta[--top];
}
q[++len]=x,vis[x]=1;
top--;
}
}
void dfs(Rint k,Rint fa)
{
for(int i=head[k],x; i ;i=t[i].next)
{
x=t[i].to;
if( vis[x] || x==fa ) continue ;
dfs( x,k );
ans=max( ans,dep[k]+dep[x]+t[i].w );
dep[k]=max( dep[k],dep[x]+t[i].w );
}
}
int main()
{
Lint tot,last;
int u,v,W;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&u,&v,&W);
add( u,v,W ),add( v,u,W );
}
Tarjan( 1,0 );
for(int i=1;i<=len;i++) dfs( q[i],0 );
for(int z=1;z<=len;z++)
{
int k=q[z];
for(int i=head[k]; i ;i=t[i].next) if( t[i].to==(z!=len ? q[z+1]:q[1]) ) { w[z]=t[i].w;break ; }
}
tot=last=0;
for(int i=1;i<=len;i++)
{
tot+=w[i-1];
sum1[i]=max( sum1[i-1],tot+dep[q[i]] );
sum2[i]=max( sum2[i-1],tot+dep[q[i]]+last );
last=max( last,dep[q[i]]-tot );
}
tot=last=0;
for(int i=len;i>=1;i--)
{
tot+=(i==len ? 0:w[i]);
Sum1[i]=max( Sum1[i+1],tot+dep[q[i]] );
Sum2[i]=max( Sum2[i+1],tot+dep[q[i]]+last );
last=max( last,dep[q[i]]-tot );
}
for(int i=1;i<len;i++) Ans=min( Ans,max( max( sum2[i],Sum2[i+1] ),sum1[i]+Sum1[i+1]+w[len] ) );
Ans=min( Ans,sum2[len] );//这句话很重要!!!
printf("%.1lf\n",max( Ans,ans )/2.0);
return 0;
}