【HDU3721】枚举+最长路

题意:给你一颗n个节点n-1条边的树,每条边都有一个权值,现在让你任意移动一条边然后把这条边连接到任意两个点上,最后问你怎样移动才能使树上相距最远的两个点距离最小。

 

思路:先求出树的最长路,然后枚举移动最长路上的所有边,移走这条边后,原树必定分为不连接的两颗子树,分别求这两颗子树的最长路,然后分别找到两颗子树最长路上靠近中点的点,那么最长路有三种情况:假设这条边为 u -> v

1.u的左子树的直径;

2.v的右子树的直径;

3.u的左子树的直径上中点分割的最长路径 +  v的右子树的直径上中点分割的最长路径 + 边权

每枚举一条边,就取这三种情况的最大值;

需要注意的事:两棵子树的最长路,一定是以整棵树的最长路的两个端点为起始点的,因此只需要预处理出所有点到两个端点的距离,然后根据删除的最长路边求两颗子树中的最大值即可。

所以我们只需2次BFS就够了;

我感觉我的代码很简短:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define N 3000
int head[N],cnt;
void ini()
{
    memset(head,-1,sizeof(head));
    cnt=0;
}
struct Edge
{
    int next,w,to;
}e[N*2];
void Add_edge(int a, int b, int c)
{
    e[cnt].to=b;e[cnt].w=c;
    e[cnt].next=head[a];
    head[a]=cnt++;
}
int dis1[N],dis2[N],fa2[N],vis[N],fa1[N],max_dis,max_id;
void BFS(int root, int dis[], int fa[])
{
    max_dis=0;max_id=root;fa[root]=root;
    memset(vis,0,sizeof(vis));
    dis[root]=0;
    queue <int> Q;
    Q.push(root);
    while(!Q.empty())
    {
        int u=Q.front();Q.pop();
        vis[u]=1;
        for(int i = head[u]; i != -1; i = e[i].next)
        {
            int v=e[i].to;
            if(vis[v])continue;
            dis[v]=dis[u]+e[i].w;
            if(max_dis<dis[v]){max_dis=dis[v],max_id=v;}
            fa[v]=u;
            Q.push(v);
        }
    }
}
void dfs(int u, int pre, int dis[]) //dis1==s为起点  dis2==t为起点
{
     for(int i = head[u]; i != -1; i = e[i].next)
     {
         int v=e[i].to;
         if(v==pre)continue;
         dfs(v,u,dis);
         if(max_dis<dis[v]){max_dis=dis[v];max_id=v;}
     }
}
int main()
{
    int T;scanf("%d",&T);
    for(int g = 1; g <= T; g++)
    {
        ini();
        int n;scanf("%d",&n);
        for(int i = 1; i < n; i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            Add_edge(a,b,c);
            Add_edge(b,a,c);
        }
        int s=0,t;
        BFS(s,dis1,fa1);
        s=max_id;
        BFS(s,dis1,fa1);
        t=max_id;
        BFS(t,dis2,fa2);
        int ans=0x7fffffff;
        for(int i = t; i != s; i=fa1[i])
        {
            int tmp_ans;
            int len=dis2[fa1[i]]-dis2[i];
            max_dis=dis2[i];
            max_id=i;
            dfs(i,fa1[i],dis2);
            tmp_ans=max_dis;
            int top=max_dis/2;
            int zhongdian,max1,max2;
            int tmp=0x7fffffff;
            zhongdian=max_id;
            for(int j = max_id; j != t; j=fa2[j])
                if(abs(dis2[j]-top)<tmp){tmp=abs(dis2[j]-top);zhongdian=j;}
            max1=max(dis2[max_id]-dis2[zhongdian],dis2[zhongdian]);
            max_dis=dis1[fa1[i]];
            max_id=fa1[i];
            dfs(fa1[i],i,dis1);
            tmp_ans=max(tmp_ans,max_dis);
            top=max_dis/2;
            tmp=0x7fffffff;
            zhongdian=max_id;
            for(int j = max_id; j != s; j=fa1[j])
                if(abs(dis1[j]-top)<tmp){tmp=abs(dis1[j]-top);zhongdian=j;}
            max2=max(dis1[max_id]-dis1[zhongdian],dis1[zhongdian]);
            tmp_ans=max(tmp_ans,max1+max2+len);
            ans=min(ans,tmp_ans);
        }
        printf("Case %d: %d\n",g,ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值