题目描述
F1 --- (13) ---- F6 --- (9) ----- F3 | | (3) | | (7) F4 --- (20) -------- F2 | | | (2) F5 | F7Being an ASCII diagram, it is not precisely to scale, of course.
7 6 1 6 13 E 6 3 9 E 3 5 7 S 4 1 3 N 2 4 20 W 4 7 2 SSample Output
52
这道题目的意思就是给一张没有环的连通无向图,求图中距离最远的两个点。假设最远的两个点之间的最远路径是 A->......->B ,在其中任取一点 C , 从 C 出发,往B的那一部分搜索,走的最远的地方一定是 B ,可以用反证法,如果不是B , 而是 D , 那么从 A->......C 再加上 C->......->D 就会比原来的更长,和假设矛盾。所以,从C->B我们先确定了直径的一个端点,紧接着就从这个端点出发确定另一个端点,因为是一棵树,从这个端点出发一定可以走到图上的任意一个点,那就包括了另一个端点,可以走得最远的那个就是另一个端点了。
如果是有向无环图,直接拓扑排序就可以求直径。
DFS深搜的做法
#include
#include
#include
using namespace std ;
int n , m ;
int head[40005] , k ;
int To[40005<<1] , Next[40005<<1] , Cost[40005<<1] ;
int dis[40005] , root , ans ;
void Add( int src , int des , int value ){
To[k] = des , Cost[k] = value , Next[k] = head[src] , head[src] = k++ ;
To[k] = src , Cost[k] = value , Next[k] = head[des] , head[des] = k++ ; // 双向存储
}
void DFS( int u , int pre ){
int v , w , i ;
for( i = head[u] ; i != -1 ; i = Next[i] ){
v = To[i] , w = Cost[i] ;
if( v == pre ) continue ; // 不能往回走
dis[v] = dis[u] + w ; // 递归的下一层需要的距离
if( dis[v] > dis[root] )
root = v ; // 寻找直径的一个端点,最远的就一定是其中一个端点
DFS( v , u ) ;
}
}
int main(){
scanf( "%d%d" , &n , &m ) ;
int i = 1 , src , des , value ;
char direct , _nbsp ;
memset( head , -1 , sizeof( head ) ) ;
k = 1 ;
for( i = 1 ; i <= m ; ++i ){
scanf( "%d%d%d%c%c" , &src , &des , &value , &_nbsp , &direct ) ; // _nbsp 吃掉空格, emm
Add( src , des , value ) ;
}
DFS( 1 , 1 ) ; // 任意找一个点出发,找第一个端点
memset( dis , 0 , sizeof( dis ) ) ;
DFS( root , root ) ; // 从找到的第一个端点出发, 找走得最远的另一个端点
for( i = 1 ; i <= n ; ++i )
ans = max( ans , dis[i] ) ;
cout << ans << endl ;
return 0 ;
}
用BFS也可以做。
#include
#include
#include
using namespace std ;
int n , m ;
int head[40005] , k ;
int To[40005<<1] , Next[40005<<1] , Cost[40005<<1] ;
int dis[40005] , root , ans ;
int Queue[40005] , front , rear ; // 队列 , front 队首 , rear 队尾
int father[40005] ; // 标记这个点是否有父亲
void Add( int src , int des , int value ){ // 邻接表双向存储
To[k] = des , Cost[k] = value , Next[k] = head[src] , head[src] = k++ ;
To[k] = src , Cost[k] = value , Next[k] = head[des] , head[des] = k++ ;
}
void BFS( int src ){
front = rear = 0 ;
int u, i , v , w ;
memset( dis , 0 , sizeof( dis ) ) ;
memset( father , 0 , sizeof( father ) ) ;
Queue[++rear] = src ; // 起点入队
while( front != rear ){
u = Queue[++front] ; // 当前点出队
for( i = head[u] ; i != -1 ; i = Next[i] ){
v = To[i] , w = Cost[i] ;
if( father[v] || v == src ) // 如果这个点有父亲,说明访问过了;或者这个点是起点
continue ;
father[v] = u ; // 标记 v 的父亲是 u
dis[v] = dis[u] + w ; // v 需要的距离
Queue[++rear] = v ; // 下一层次入队
if( dis[v] > dis[root] ) root = v ; // 找最远的那个端点
}
}
}
int main(){
scanf( "%d%d" , &n , &m ) ;
int i = 1 , src , des , value ;
char direct , nbsp ;
memset( head , -1 , sizeof( head ) ) ;
k = 1 ;
for( i = 1 ; i <= m ; ++i ){
scanf( "%d%d%d%c%c" , &src , &des , &value , , &direct ) ;
Add( src , des , value ) ;
}
BFS( 1 ) ;
BFS( root ) ;
for( i = 1 ; i <= n ; ++i )
ans = max( ans , dis[i] ) ;
cout << ans << endl ;
return 0 ;
}
以上的都是常规的方法。
我后来想尝试用 Disjkstra 来求一张图中的直径,不过我得把边都变成负的,这样求出来的最小值,再添加一个-负号就是最大值了。 统计每个点的度,度为 1 的点就是直径的起点和终点,因为如果直径的某个端点度 > 1 , 从一条边进来,就肯定可以找到另外一条边出去,那就不是直径了,因为路径还可以再变长。找到多个度为 1 的点,然后选择一个点作为起点,其它度为1 的点全部连向 n+1编号的点,边的权值设为 0 , 这样一来,就只要求从这个起点到 n+1 的最小值,最后加一个负号就是最大值了。
很可惜,一直超时,尽管我用了优先级队列.......
复杂度是 O(logn) , 比上面的 BFS 和 DFS 的复杂度要高。从做题的角度来说,没这个必要,而且这个效率也不高。不过我感觉这个思路挺不错的,也放在这里吧。
如果思路逻辑有问题,或者可以优化,请指正。
// 超时, emm
#include
#include
#include
#include
using namespace std ;
#define INF 0x3f3f3f3f
typedef pair P ;
int n , m ;
int head[40005] , k ;
int To[40005<<1] , Next[40005<<1] , Cost[40005<<1] ;
int dis[40005] ;
int In[40005] , book[40005] ;
void Add( int src , int des , int value ){
To[k] = des , Cost[k] = value , Next[k] = head[src] , head[src] = k++ ;
To[k] = src , Cost[k] = value , Next[k] = head[des] , head[des] = k++ ;
}
void Disjkstra( int src ){
priority_queue< P , vector , greater
> One ;
memset( dis , INF , sizeof( dis ) ) ;
int u , v , w , i ;
dis[src] = 0 ;
One.push( P( 0 , src ) ) ;
while( !One.empty() ){
P top = One.top() ;
One.pop() ;
u = top.second ;
if( book[u] )
continue ;
book[u] = 1 ;
for( i = head[u] ; i != -1 ; i = Next[i] ){
v = To[i] , w = Cost[i] ;
if( dis[v] > dis[u] + w )
One.push( P( dis[v] = dis[u] + w , v ) ) ;
}
}
cout << -dis[n+1] << endl ;
}
int main(){
scanf( "%d%d" , &n , &m ) ;
int i = 1 , src , des , value ;
char direct ;
memset( head , -1 , sizeof( head ) ) ;
k = 1 ;
for( i = 1 ; i <= m ; ++i ){
scanf( "%d%d%d %c" , &src , &des , &value , &direct ) ;
Add( src , des , -value ) ; // 初始化添加负权边
In[des]++ ;
In[src]++ ; // 统计每个点的度
}
int first = 1 ;
for( i = 1 ; i <= n ; ++i )
if( In[i] == 1 ){
if( first == 1 ) src = i , first = 0 ; // 找一个点做起点
else Add( i , n+1 , 0 ) ; // 添加一个总汇 , n+1 , 边权值为 0
}
Disjkstra( src ) ;
return 0 ;
}