[NOI2013]快餐店

14 篇文章 0 订阅
2 篇文章 0 订阅

题意

  给定一个 n n 个点,n条边的连通图,求直径(最远点对距离)

题解

  如果是树,那么大家都晓得答案肯定是直径的一半,其实这个结论推广到本题也是正确的(有人会证吗?我不会……)
  考虑当 n2000 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 个点与第i+1个点之间的边,能够产生的直径就是 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(n)

代码

#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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值