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

题目大意:给你一棵有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的更多相关文章

  1. HDU 3726 Graph and Queries(平衡二叉树)(2010 Asia Tianjin Regional Contest)

    Description You are given an undirected graph with N vertexes and M edges. Every vertex in this grap ...

  2. HDU-4432-Sum of divisors ( 2012 Asia Tianjin Regional Contest )

    Sum of divisors Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  3. hdu oj 3127 WHUgirls(2009 Asia Wuhan Regional Contest Online)

    WHUgirls Time Limit: 3000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total ...

  4. HDU 3695 / POJ 3987 Computer Virus on Planet Pandora(AC自动机)(2010 Asia Fuzhou Regional Contest)

    Description Aliens on planet Pandora also write computer programs like us. Their programs only consi ...

  5. HDU 3686 Traffic Real Time Query System(双连通分量缩点+LCA)(2010 Asia Hangzhou Regional Contest)

    Problem Description City C is really a nightmare of all drivers for its traffic jams. To solve the t ...

  6. HDU 3698 Let the light guide us(DP+线段树)(2010 Asia Fuzhou Regional Contest)

    Description Plain of despair was once an ancient battlefield where those brave spirits had rested in ...

  7. HDU 3685 Rotational Painting(多边形质心+凸包)(2010 Asia Hangzhou Regional Contest)

    Problem Description Josh Lyman is a gifted painter. One of his great works is a glass painting. He c ...

  8. HDU 4433 locker 2012 Asia Tianjin Regional Contest 减少国家DP

    意甲冠军:给定的长度可达1000数的顺序,图像password像锁.可以上下滑动,同时会0-9周期. 每个操作.最多三个数字连续操作.现在给出的起始序列和靶序列,获得操作的最小数量,从起始序列与靶序列 ...

  9. HDU 4436 str2int(后缀自动机)(2012 Asia Tianjin Regional Contest)

    Problem Description In this problem, you are given several strings that contain only digits from '0' ...

随机推荐

  1. segv & mini coredump

    1. mini coredump    a. segv      http://zh.scribd.com/doc/3726406/Crash-N-Burn-Writing-Linux-applica ...

  2. poj Cash Machine

    http://poj.org/problem?id=1276 #include<cstdio> #include<cstring> #include<cmath> ...

  3. poj 2488A Knight's Journey

    #include<cstdio> #include<cstring> #define MAXN 26 using namespace std; ,,-,,-,,-,}; ,-, ...

  4. Java RMI 入门案例

    Java Remote Method Invocation(Java RMI) 是一个 Java API, 执行远程方法的调用,相当于 Remote Procedure Calls(RPC).Java ...

  5. apt-mirror

    对于centos来说,搭建一个本地源,相对来说,还是比较简单的.对于ubuntu来说,就复杂很多, 估计也和我不熟悉有关. http://unixrob.blogspot.com/2012/05/cr ...

  6. WPF: How to use DatePicker in XAML-DataGrid

    https://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn(v=VS.100).as ...

  7. maven项目中找不到Maven Dependencies解决办法

    用eclipse创建maven项目后,在Deployment Assembly中通过Add...->Java Build Path Entries导入Maven Dependencies时,发现 ...

  8. 微信小程序简易教程

    刚接触到微信小程序开发,这里做一个简单的教程: 1. 获取微信小程序的 AppID 登录 https://mp.weixin.qq.com ,就可以在网站的"设置"-"开 ...

  9. android学习之4种点击事件的响应方式

    如题,下面就一一列出对点击事件响应的4种方式: 第一种:内部类的形式: package com.example.dail; import android.net.Uri; import android ...

  10. 第一章 工欲善其事 必先利其器—Android SDK工具(3)

    1.3没有真机一样开发--Android模拟器 有些时候,我们手头上可能并没有符合要求的Android设备.那么这时候我们是不是对调试或者开发就一筹莫展了呢?当然不是.由于我们有Android模拟器. ...