树的直径hdu3721

F.A.Q
Hand In Hand
Online Acmers
Forum | Discuss
Statistical Charts
Problem Archive
Realtime Judge Status
Authors Ranklist
 
      C/C++/Java Exams     
ACM Steps
Go to Job
Contest LiveCast
ICPC@China
Best Coder beta
VIP | STD Contests
Virtual Contests 
    DIY | Web-DIY beta
Recent Contests

Building Roads

Time Limit: 16000/8000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 759    Accepted Submission(s): 267


Problem Description
There is a magic planet in the space. There is a magical country on the planet. There are N cities in the country. The country is magical because there are exactly N-1 magic roads between the N cities, and from each city, it is possible to visit any other city. But after the huge expansion of the country, the road system seems to be messy. The moderator decided to rebuild the road system.

As a worker, I cannot do too much things. I could just move one road to another position connecting arbitrary 2 cities using my magic, keeping its length unchanged. Of course, afterwards all the N cities have to be still connected. I wonder how to move in order to make the farthest distance between any two cities minimum. Could you solve it for me?
 

Input
The first line of the input is one integer T (T ≤ 10), and then T test cases follow.
Each test case begins with a line contains only one integer N (N ≤ 2500), means there are N magic cities. The cities are numbered from 0 to N-1.
Following N-1 lines, each line has 3 integers a, b and c, means there is a magic road between a and b with distance c. (0 <= a, b < N, 0 < c <= 1000)
 

Output
For each test case, output the case number and the corresponding minimum farthest distance. See sample for detailed format.
 

Sample Input
      
      
2 4 0 1 2 1 2 2 2 3 2 5 0 1 1 1 2 2 2 3 3 3 4 4
 

Sample Output
      
      
Case 1: 4 Case 2: 7

题意:给一棵树,可以移动树上的一条边,但是必须保证移动之后的图还是一棵树,问如何移动才能使得移动之后的树的直径最短。


思路:调整的边肯定在树的直径上,所以可以先求出树的直径,然后枚举每条边,然后判断

只有三种情况:1.左子树的直径,2.右子树的直径,3.左子树的中点到右子树的中点(中点:树中的每一点到该点的最大值最小)

如何求子树的直径:子树的直径一定是以原来整棵树的直径的端点为端点的,所以可以先处理处每个点到端点的距离,然后dfs找出最远的点。

如何找出树的中点:中点肯定在树的直径上。

证明:假设这样一个点a不在直径上,那么它到最远距离的点,一定会和直径产生一个交点b(由直径的性质),那么a到其他点(设为x)的最大距离一定大于b到其他点(直径的端点,设为y)的最大距离。所以a一定在直径上

证明:其实x点就是直径的端点,因为,若x不是直径的端点,那么就有ax>ay ==>bx>by==>那么y就不应该是直径的端点了。而是x。所以x一定是直径的端点。。所以ax=ab+bx,所以ax>bx。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
const int maxn=3000;
const int INF=1000000000;

struct node
{
    int v,next,f;
}edge[maxn*2];
int n,s,t,maxid,maxdis;
int head[maxn],num,dis1[maxn],dis2[maxn],fa1[maxn],fa2[maxn],vis[maxn];

void init()
{
    num=0;
    memset(head,-1,sizeof(head));
}
void add_edge(int a,int b,int f)
{
    edge[num].v=b;
    edge[num].next=head[a];
    edge[num].f=f;
    head[a]=num++;
}
void bfs(int st,int dis[],int fa[])
{
    queue<int> q;
    maxdis=dis[st]=0;
    maxid=st;
    q.push(st);
    memset(vis,0,sizeof(vis));
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v,w=edge[i].f;
            if(vis[v])continue;
            dis[v]=dis[u]+w;
            if(maxdis<dis[v]){maxdis=dis[v];maxid=v;}
            fa[v]=u;
            q.push(v);
        }
    }
}
void dfs(int u,int fa,int dis[])
{
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==fa)continue;
        if(dis[v]>maxdis){maxdis=dis[v];maxid=v;}
        dfs(v,u,dis);
    }
}

void solve()
{
    bfs(0,dis1,fa1);
    s=maxid;
    bfs(s,dis1,fa1);
    t=maxid;
    bfs(t,dis2,fa2);
    int ans=INF;
    for(int i=t;i!=s;i=fa1[i])
    {
        int maxl,maxr,midl,midr;
        int len=dis2[fa1[i]]-dis2[i];
        //右子树的直径
        maxid=i,maxdis=dis2[i];
        dfs(i,fa1[i],dis2);

        //右子树的中点,中点一定在直径上
        int sr=maxid;
        int tmp=dis2[sr]/2,tmp1=INF;
        midl=sr;
        for(int j=sr;j!=t;j=fa2[j])
            if(abs(dis2[j]-tmp)<tmp1){tmp1=abs(dis2[j]-tmp);midl=j;}//cout<<1;
        maxl=max(dis2[sr]-dis2[midl],dis2[midl]);

        //左子树的直径
        maxid=fa1[i],maxdis=dis1[fa1[i]];
        dfs(fa1[i],i,dis1);
        //左子树的中点,中点一定在直径上
        int tl=maxid;
        tmp=dis1[tl]/2,tmp1=INF;
        midr=tl;
        for(int j=tl;j!=s;j=fa1[j])
            if(abs(dis1[j]-tmp)<tmp1){tmp1=abs(dis1[j]-tmp);midr=j;}
        maxr=max(dis1[tl]-dis1[midr],dis1[midr]);

        ans=min(ans,max(max(dis1[tl],dis2[sr]),maxr+maxl+len));
    }
    printf("%d\n",ans);
}
int main()
{
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        init();
        for(int i=1;i<n;i++)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            add_edge(u,v,d);
            add_edge(v,u,d);
        }
        printf("Case %d: ",cas++);
        solve();
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值