zoukankan      html  css  js  c++  java
  • HDU 3721 Building Roads (2010 Asia Tianjin Regional Contest)

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

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


  • 相关阅读:
    51nod 1138 【数学-等差数列】
    hdoj3665【简单DFS】
    hdoj3664【DP】
    51nod1270 【dp】
    51nod 1069【思维】
    关于一些数学符号和概率的阐述;
    51nod 1428【贪心】
    51nod 1133【贪心】
    51nod1127【尺取】
    51nod1126【矩阵快速幂】
  • 原文地址:https://www.cnblogs.com/aukle/p/3215151.html
Copyright © 2011-2022 走看看