[BZOJ 1509][NOI 2003]逃学的小孩(树的直径)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1509

题目大意

要从一棵树中找出三个点 X,Y,Z ,使得 min(dis[A][C],dis[B][C])+dis[A][B] 最大,求这个最大值

思路

大部分的做法都是基于枚举分叉点的树上DP。这种做法非常好想,但是还是有些难写。
实际上可以发现,min里头的两个东西具体取哪个并不重要,或者说点C距离A更近还是距离B更近并不重要。下面给出一个结论: min(dis[A][C],dis[B][C])+dis[A][B] 取最大值时,路径 AB 是整个树的直径(最长链),通过BFS找出树的直径后,直接枚举点 C 即可得到最大答案。

关于此题这种做法的具体证明见http://blog.sina.com.cn/s/blog_72aa02bd0100y5vt.html

代码

#include 
#include 
#include 
#include 
#include 
#include 

#define MAXN 210000

using namespace std;

typedef long long int LL;

struct edge
{
    int u,v,w,next;
}edges[MAXN*2];

int head[MAXN],nCount=0;

void AddEdge(int U,int V,int W)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].w=W;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

int n,m;
bool vis[MAXN];
int q[MAXN];

int BFS(int S,LL dist[]) //求出每个点到S的距离,保存在dist[]数组里,并返回距离S最远的点
{
    int farthest=0; //最远点
    memset(vis,false,sizeof(vis));
    int h=0,t=1;
    dist[S]=0; //!!!!
    q[h]=S;
    vis[S]=true;
    while(hint u=q[h++];
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(vis[v]) continue;
            dist[v]=dist[u]+edges[p].w; //!!!!
            if(dist[v]>dist[farthest]) farthest=v;
            q[t++]=v;
            vis[v]=true;
        }
    }
    return farthest;
}

LL dista[MAXN],distb[MAXN];

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        AddEdge(u,v,w);
        AddEdge(v,u,w);
    }
    int a=BFS(1,dista);
    int b=BFS(a,dista);
    BFS(b,distb);
    LL ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,min(dista[i],distb[i]));
    printf("%lld\n",ans+dista[b]);
    return 0;
}

你可能感兴趣的:(BZOJ,图论,NOI,传统题)