您的位置:首页 > 其它

POJ 1985 Cow Marathon 求直径 DFS基础 和 BFS 基础

2018-02-03 11:12 309 查看
题目描述

Farmer John's pastoral neighborhood has N farms (2 <= N <= 40,000), usually numbered/labeled 1..N. A series of M (1 <= M < 40,000) vertical and horizontal roads each of varying lengths (1 <= length <= 1000) connect the farms. A
map of these farms might look something like the illustration below in which farms are labeled F1..F7 for clarity and lengths between connected farms are shown as (n): 
F1 --- (13) ---- F6 --- (9) ----- F3

|                                 |

(3)                                |

|                                (7)

F4 --- (20) -------- F2            |

|                                 |

(2)                               F5

|

F7

Being an ASCII diagram, it is not precisely to scale, of course. 

Each farm can connect directly to at most four other farms via roads that lead exactly north, south, east, and/or west. Moreover, farms are only located at the endpoints of roads, and some farm can be found at every endpoint of every road. No two roads cross,
and precisely one path (sequence of roads) links every pair of farms. 

After hearing about the epidemic of obesity in the USA, Farmer John wants his cows to get more exercise, so he has committed to create a bovine marathon for his cows to run. The marathon route will include a pair of farms and a path comprised of a sequence
of roads between them. Since FJ wants the cows to get as much exercise as possible he wants to find the two farms on his map that are the farthest apart from each other (distance being measured in terms of total length of road on the path between
the two farms). Help him determine the distances between this farthest pair of farms.

Input
* Line 1: Two space-separated integers: N and M 

* Lines 2..M+1: Each line contains four space-separated entities, F1, F2, L, and D that describe a road. F1 and F2 are numbers of two farms connected by a road, L is its length, and D is a character that is either 'N', 'E', 'S', or 'W' giving the direction
of the road from F1 to F2.
4000

Output
* Line 1: An integer giving the distance between the farthest pair of farms.

Sample Input
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 S


Sample Output
52

这道题目的意思就是给一张没有环的连通无向图,求图中距离最远的两个点。假设最远的两个点之间的最远路径是 A->......->B ,在其中任取一点 C , 从 C 出发,往B的那一部分搜索,走的最远的地方一定是 B ,可以用反证法,如果不是B , 而是 D , 那么从 A->......C 再加上 C->......->D 就会比原来的更长,和假设矛盾。所以,从C->B我们先确定了直径的一个端点,紧接着就从这个端点出发确定另一个端点,因为是一棵树,从这个端点出发一定可以走到图上的任意一个点,那就包括了另一个端点,可以走得最远的那个就是另一个端点了。

如果是有向无环图,直接拓扑排序就可以求直径。

DFS深搜的做法

#include <iostream>
#include <cstdio>
#include <cstring>
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 <iostream>
#include <cstdio>
#include <cstring>
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 <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std ;
#define INF 0x3f3f3f3f
typedef pair<int,int> 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<P> , greater<P> > 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 ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: