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

来源:互联网 发布:京东为什么比淘宝贵 编辑:程序博客网 时间:2024/05/27 00:52

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

        题目大意:给你一棵有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 ;}


原创粉丝点击