最近在做最短路徑的題目,有幾道題是同一個類型的,題意基本都是尋找從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 ;
總結:
遇到題目多想想有沒有其他的解法,不要只想着用最粗暴的方法能夠解決就行,這個地方好使,在其他地方就不一定好用了,多考慮高效率的方法