感慨一下,区域赛的题目果然很费脑啊!!不过确实是一道不可多得的好题目!!
题目大意:给你一棵有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 ; }