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 ;}
- HDU 3721 Building Roads (2010 Asia Tianjin Regional Contest) - from lanshui_Yang
- hdu 3729 2010 Asia Tianjin Regional Contest
- hdu 3722 2010 Asia Tianjin Regional Contest Card Game KM
- HDU 3723 Delta Wave(2010 Asia Tianjin Regional Contest )
- HDU 3724 Encoded Barcodes (2010 Asia Tianjin Regional Contest )
- 2012 Asia Tianjin Regional Contest
- 2012 Asia Tianjin Regional Contest
- 2012 Asia Tianjin Regional Contest
- HDU-4435-charge-station ( 2012 Asia Tianjin Regional Contest )
- HDU 3729 I'm Telling the Truth(2010 Asia Tianjin Regional Contest )
- 【挖坑】 2012 Asia Tianjin Regional Contest
- 2012 Asia Tianjin Regional Contest - str2int
- 2012 Asia Tianjin Regional Contest - Queue Sequence
- 2012 Asia Tianjin Regional Contest - Mahjong
- 2010 Asia Regional Tianjin Site —— Online Contest hdu 3622 Bomb Game 强连通 two sat
- HDU 4433 locker 2012 Asia Tianjin Regional Contest 状态压缩DP
- HDU-4432-Sum of divisors ( 2012 Asia Tianjin Regional Contest )
- 2012 Asia Tianjin Regional Contest - locker 暴力DP
- 0. SQL Server监控清单
- 10个你应该知道的Java正则表达式的例子
- EOJ 1095 Guess the Number
- 孙鑫VC学习(第20课-HOOK过程和数据库)
- Park Visit
- HDU 3721 Building Roads (2010 Asia Tianjin Regional Contest) - from lanshui_Yang
- wei xin
- socket网络编程之TCP_Server
- Hadoop集群(第5期)_Hadoop安装配置
- HDU 4608
- TabControl中内嵌窗体,如何触发内嵌窗体的关闭事件?
- OLE操作Excel编译错误处理
- 使用PowerDesigner进行数据库设计
- Linux chkconfig详解