现在的位置: 首页 > 综合 > 正文

HDU 3721 Building Roads (2010 Asia Tianjin Regional Contest) – from lanshui_Yang

2018年02月21日 ⁄ 综合 ⁄ 共 3456字 ⁄ 字号 评论关闭

        感慨一下,区域赛的题目果然很费脑啊!!不过确实是一道不可多得的好题目!!

        题目大意:给你一棵有n个节点的树,让你移动树中一条边的位置,即将这条边连接到任意两个顶点(边的大小不变),要求使得到的新树的直径最小。

        解题思路:此题先求出原始树的直径maxr1,并记录直径上的各个节点。很容易想到要移动的边一定是直径上的边,只有这样才有可能使树的直径减小!! 接着就是枚举直径上的每条边,并用这条边作为分隔将原始树分割成两棵子树(即子树一和子树二),然后分别求子树一的直径maxr2 和子树二的直径maxr3。再找出子树一的直径的中点 和 子树二的直径的中点(这里的中点是指树中离树的直径的端点距离最小的点),将移动的边连接在这两个中点上,这样才能使生成的新树的直径sumtmp最小。最后求出min
{ maxr1 , maxr2 ,maxr3 ,sumtmp }即可。

        Ps : 此题运用了很多技巧 ,如怎样找子树的中点?  生成两棵子树时是否要在图邻接表中删除此边 ?这些都有技巧,我这里找树的中点的算法的复杂度是O(n) ,具体详解请看代码:

// G++ 109ms AC
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std ;
int n ;
const int MAXN = 3000 ;
int path[MAXN] ;    // 记录bfs路径
int shortest[MAXN] ;  // 记录原始树的直径上的点
int shortmp2[MAXN] ;  // 记录原始树拆分后的子树一的直径上的点
int shortmp3[MAXN] ;  // 记录子树二的直径上的点
const int INF = 0x7fffffff ;
struct Node
{
    int adj ;
    int d ;
    Node * next ;
};
Node * vert[MAXN] ;
int vis[MAXN] ;
int dis[5][MAXN] ;
int maxr[5];
queue<int> q ;
int bfs(int start , int xu)   // 找树的直径
{
    memset(dis[xu] , 0 , sizeof(dis[xu])) ;
    maxr[xu] = 0 ;
    while (!q.empty())  // 清空队列
    {
        q.pop() ;
    }
    int ans = start ;   // ans 为直径的端点 ,注意,此处一定要把ans初始化为start !!
                        //因为当子树只有一个节点时,它的直
                        //径的两个端点均为start
    vis[start] = 1 ;
    dis[xu][start] = 0 ;
    Node * p ;
    int tmp ;
    q.push(start) ;
    while (!q.empty())
    {
        tmp = q.front() ;
        vis[tmp] = 1 ;
        q.pop() ;
        p = vert[tmp] ;
        while (p != NULL)
        {
            int tp2 = p -> adj ;
            if(!vis[tp2])
            {
                vis[tp2] = 1 ;
                if(dis[xu][tp2] < dis[xu][tmp] + p -> d)
                {
                    dis[xu][tp2] = dis[xu][tmp] + p -> d ;
                    path[tp2] = tmp ;
                }
                if(maxr[xu] < dis[xu][tp2])
                {
                    maxr[xu] = dis[xu][tp2] ;
                    ans = tp2 ;
                }
                q.push(tp2) ;
            }
            p = p -> next ;
        }
    }
    return ans ;
}
int fz(int x , int y)
{
    int sumtmp = 0 ;
    memset(vis , 0 , sizeof(vis)) ;
    memset(path , -1 , sizeof(path)) ;
    vis[x] = vis[y] = 1 ;  // 这是把树分割成两棵子树的技巧,不需
                           //把邻接表中的边(x , y) 删去,只需事
                           //先标记x和y即可
    int dr2 , dl2 ;
    dr2 = bfs(x , 2) ;
    memset(vis , 0 , sizeof(vis)) ;
    memset(path , -1 , sizeof(path)) ;
    vis[y] = 1 ;  // 注意这里 !!
    dl2 = bfs(dr2 , 2) ;
    int k = 0 ;
    shortmp2[k] = dl2 ;  // 记录子树一的直径上的点
    while (path[shortmp2[k]] != -1)
    {
        k ++ ;
        shortmp2[k] = path[shortmp2[k - 1]] ;
    }
    int ce2 ;
    int maxt = INF ;
    int j ;
    for(j = 0 ; j <= k ; j ++)  // 下面的过程为找子树一的直径的中点,比较重要
    {
        if(abs(maxr[2] - 2 * dis[2][shortmp2[j]]) < maxt)
        {
            maxt = abs(maxr[2] - 2 * dis[2][shortmp2[j]]) ;
            ce2 = max(maxr[2] - dis[2][shortmp2[j]] , dis[2][shortmp2[j]]) ;
        }
    }

    // 下面是求子树二的直径和其直径的中点,方法与子树一相同
    memset(vis , 0 , sizeof(vis)) ;
    memset(path , -1 , sizeof(path)) ;
    vis[x] = vis[y] = 1 ;
    int dr3 , dl3 ;
    dr3 = bfs(y , 3) ;
    memset(vis , 0 , sizeof(vis)) ;
    memset(path , -1 , sizeof(path)) ;
    vis[x] = 1 ;
    dl3 = bfs(dr3 , 3) ;
    k = 0 ;
    shortmp3[k] = dl3 ;
    while (path[shortmp3[k]] != -1)
    {
        k ++ ;
        shortmp3[k] = path[shortmp3[k - 1]] ;
    }
    maxt = INF ;
    int ce3 ;
    for(j = 0 ; j <= k ; j ++)
    {
        if(abs(maxr[3] - 2 * dis[3][shortmp3[j]]) < maxt)
        {
            maxt = abs(maxr[3] - 2 * dis[3][shortmp3[j]]) ;
            ce3 = max(maxr[3] - dis[3][shortmp3[j]] , dis[3][shortmp3[j]]) ;
        }
    }

    // 以下是找出子树一、子树二和连接子树一和二得到的新树的直径的最大值
    sumtmp = ce2 + ce3 + abs(dis[1][x] - dis[1][y]) ;
    sumtmp = max(sumtmp , maxr[2]) ;
    sumtmp = max(sumtmp , maxr[3]) ;
    return sumtmp ;
}
void jie() // 求解本题
{
    // 先求出原始树的直径以及直径上的点
    memset(path , -1 , sizeof(path)) ;
    memset(vis , 0 , sizeof(vis)) ;
    int dr1 = bfs(0 , 1) ;
    memset(vis , 0 , sizeof(vis)) ;
    memset(path , -1 , sizeof(path)) ;
    int dl1 = bfs(dr1 , 1) ;
    int k = 0 ;
    shortest[k] = dl1 ;
    while (path[shortest[k]] != -1)
    {
        k ++ ;
        shortest[k] = path[shortest[k - 1]] ;
    }
    int  j ;
    int maxans = maxr[1] ;
    for( j = 0 ; j <= k ; j ++) // 枚举直径上的边,把原始树分割成两棵子树
    {
        int maxtmp = fz(shortest[j] ,shortest[j + 1]) ;
        if(maxans > maxtmp)
        {
            maxans = maxtmp ;
        }
    }
    printf("%d\n" , maxans) ;
}
void dele()
{
    Node * p ;
    int i ;
    for(i = 0 ; i < n ; i ++)
    {
        p = vert[i] ;
        while (p != NULL)
        {
            vert[i] = p -> next ;
            delete p ;
            p = vert[i] ;
        }
    }
}
int main()
{
    int t ;
    scanf("%d" , &t) ;
    int cnt ;
    for(cnt = 1 ; cnt <= t ; cnt ++)
    {
        memset(vert , 0 , sizeof(vert)) ;
        scanf("%d" , &n) ;
        int i ;
        for(i = 1 ; i <= n - 1 ; i ++)
        {
            int a , b , c ;
            scanf("%d%d%d" , &a , &b , &c) ;  // 建图
            Node * p ;
            p = new Node ;
            p -> adj = b ;
            p -> d = c ;
            p -> next = vert[a] ;
            vert[a] = p ;

            p = new Node ;
            p -> adj = a ;
            p -> d = c ;
            p -> next = vert[b] ;
            vert[b] = p ;
        }
        printf("Case %d: " , cnt) ;
        jie() ;
        dele() ; //释放图
    }
    return 0 ;
}

抱歉!评论已关闭.