最近在做最短路径的题目,有几道题是同一个类型的,题意基本都是寻找从A到B所有路径中,路径的最大边的最小值。这样类型的题有POJ - 2253 Frogger, UVA - 10048 Audiophobia,POJ - 1797 Heavy Transportation
这样的题有很多种解题方法,最简单粗暴的就是用Floyd,还可以用Dijkstra,也可以用最小生成树Kruskal
我以POJ - 2253 Frogger这道题作为例子讲讲我对这种题目的理解
题意:
一只青蛙想从A点跳到B点,青蛙可以选择通过不同的点进行跳跃最后到达终点,需要找出所有的路径中两点间距离最大边的最小值 。
理解:
每一条路径都是由多个点构成的,每两个点之间都会有一段距离,在一条路径上找出其中距离最大的那一条边,然后与其他路径的最大边进行比较,找出最大边最小的值
最常规的最短路径中,Floyd和Dijkstra存两点间距离的数组一般都是存两点之间所有路径中互相到达需要的最短路径值,而现在需要存的两点到达需要的最大边的最小值。
以Floyd为例:
常规写法:(比较原来的最小值和通过点k的值哪个小就存哪个)
for(int k=0; k<n; k++) {
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
map[i][j] = min(map[i][j], map[i][k]+map[k][j]) ;
}
}
}
该类型写法:(通过点k会生成两条新的边,要找出其中最大的边,再与原来存的该路径上的最大边进行比较哪个小就存哪个)
for(int k=0; k<n; k++) {
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
map[i][j] = min(map[i][j], max(map[i][k], map[k][j])) ;
}
}
}
完整代码如下:
Floyd
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std ;
int main() {
int n, x[205], y[205], count=0 ; //记录每一个点的坐标
double map[205][205] ; //存值
float dis ;
while(scanf("%d", &n) && n) {
for(int i=0; i<n; i++) //先用数组存每一个点,每一个点按序号设为0~n-1号城市
scanf("%d%d", &x[i], &y[i]) ;
for(int i=0; i<n; i++) {
for(int j=i; j<n; j++) { //无向图,i之前的不用重复计算
float a = ((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]))* 1.0 ;
dis = sqrt(a) ;
map[i][j] = map[j][i] = dis ; //两个点之间的距离存进map里
}
}
for(int k=0; k<n; k++) {
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
map[i][j] = min(map[i][j], max(map[i][k], map[k][j])) ;
}
}
}
printf("Scenario #%d\n", ++count) ;
printf("Frog Distance = %.3f\n\n", map[0][1]) ;
}
}
Floyd比较好写,但有点费时,在POJ - 1797 Heavy Transportation中就会超时,可以选择Dijkstra,Kruskal来做,思路都是相似的
Dijkstra
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<string.h>
using namespace std ;
int main() {
int n, x[205], y[205], count=0 ;
float map[205][205] ;
float dis[205] ;
int book[205] ;
while(scanf("%d", &n) && n) {
for(int i=0; i<n; i++) //先用数组存每一个点,每一个点按序号设为0~n-1号城市
scanf("%d%d", &x[i], &y[i]) ;
for(int i=0; i<n; i++) {
for(int j=i; j<n; j++) {
float a = ((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]))* 1.0 ;
map[i][j] = map[j][i] = sqrt(a) ;
}
}
for(int i=0; i<n; i++) //初始化每个点到起点的距离
dis[i] = map[0][i] ;
memset(book, 0, sizeof(book)) ;
book[0] = 1 ;
for(int i=1; i<n; i++) {
float temp = 9999999.0;
int u = 1 ;
for(int j=0; j<n; j++) { //找到最优点设为确定值
if(book[j]==0 && dis[j]<temp) { //从最小的开始找确定值
temp = dis[j] ;
u = j ;
}
}
book[u] = 1 ;
if(u==1) //终点的下标是1
break ;
for(int j=0; j<n; j++) {
float a = max(map[u][j], temp) ; //通过u点会生成两条边,找出其中最大值
if(book[j]==0 && a<dis[j]) //起点到j所有路径中最大边中的最小值
dis[j] = a ; //更新距离
}
}
printf("Scenario #%d\n", ++count) ;
printf("Frog Distance = %.3f\n\n", dis[1]) ; //终点在第二个
}
return 0 ;
}
Kruskal
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std ;
struct edge {
int from ;
int to ;
float dis ;
};
struct edge e[40005] ;
int f[205] ;
bool com(edge x, edge y) {
return x.dis<y.dis ;
}
int getf(int x) {
return f[x]==x ? f[x]:f[x]=getf(f[x]) ;
}
int merge(int x, int y) {
int t1, t2 ;
t1 = getf(x) ;
t2 = getf(y) ;
if(t1 != t2) {
f[t2] = t1 ;
return 1 ;
}
return 0 ;
}
int main() {
int n, x[205], y[205], time=0 ;
while(scanf("%d", &n) && n) {
int count=0 ;
for(int i=0; i<n; i++) //先用数组存每一个点,每一个点按序号设为0~n-1号城市
scanf("%d%d", &x[i], &y[i]) ;
for(int i=0; i<n; i++) {
for(int j=i+1; j<n; j++) {
float a = ((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j])) ;
e[count].from = i ;
e[count].to = j ;
e[count++].dis = sqrt(a) ;
}
}
sort(e, e+count, com) ; //先将所有点按距离从小到大排序
float ans ;
for(int i=0; i<=n; i++) //初始化查并集
f[i] = i ;
for(int i=0 ;i<count; i++) {
if(merge(e[i].from, e[i].to)) {
ans = e[i].dis ; //已经从小到大排序,e[i].dis就是当前的最大边的最小值
if(getf(1) == getf(2)) //起点终点在同一个集合中则结束
break ;
}
}
printf("Scenario #%d\n", ++time) ;
printf("Frog Distance = %.3f\n\n", ans) ;
}
return 0 ;
总结:
遇到题目多想想有没有其他的解法,不要只想着用最粗暴的方法能够解决就行,这个地方好使,在其他地方就不一定好用了,多考虑高效率的方法