最短路问题总结

Floyd算法:

可以求得每两点间的最短路,但时间复杂度较高,一般不用。

例题:Cow Contest   POJ3660

N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, are participating in a programming contest. As we all know, some cows code better than others. Each cow has a certain constant skill rating that is unique among the competitors.

The contest is conducted in several head-to-head rounds, each between two cows. If cow A has a greater skill level than cow B (1 ≤ A ≤ N; 1 ≤ B ≤ NA ≠ B), then cow A will always beat cow B.

Farmer John is trying to rank the cows by skill level. Given a list the results of M(1 ≤ M ≤ 4,500) two-cow rounds, determine the number of cows whose ranks can be precisely determined from the results. It is guaranteed that the results of the rounds will not be contradictory.

Input

* Line 1: Two space-separated integers: N and M
* Lines 2..M+1: Each line contains two space-separated integers that describe the competitors and results (the first integer, A, is the winner) of a single round of competition: A and B

Output

* Line 1: A single integer representing the number of cows whose ranks can be determined
 

Sample Input

5 5
4 3
4 2
3 2
1 2
2 5

Sample Output

2
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
const int num=105;
const int inf=0x3f3f3f3f;
int n,dis[num][num];
int cnt[num];
void Floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dis[i][j]>dis[i][k]+dis[k][j])
                    dis[i][j]=dis[i][k]+dis[k][j];
}
int main()
{
    int m;
    cin>>n>>m;
    int a,b;
    memset(dis,inf,sizeof(dis));
    for(int i=1;i<=n;i++)
        dis[i][i]=0;
    for(int i=0;i<m;i++){
        cin>>a>>b;
        dis[a][b]=0;
    }
    Floyd();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(dis[i][j]!=inf){
                cnt[i]++;
                cnt[j]++;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(cnt[i]==n+1)
            ans++;
    }
    cout<<ans<<endl;
    return 0;
}

Bellman_Ford算法:

一般用于检测负环

例题:Wormholes  POJ 3259

农夫约翰在探索他的许多农场,发现了一些惊人的虫洞。虫洞是很奇特的,因为它是一个单向通道,可让你进入虫洞的前达到目的地!他的N(1≤N≤500)个农场被编号为1..N,之间有M(1≤M≤2500)条路径,W(1≤W≤200)个虫洞。FJ作为一个狂热的时间旅行的爱好者,他要做到以下几点:开始在一个区域,通过一些路径和虫洞旅行,他要回到最开时出发的那个区域出发前的时间。也许他就能遇到自己了:)。为了帮助FJ找出这是否是可以或不可以,他会为你提供F个农场的完整的映射到(1≤F≤5)。所有的路径所花时间都不大于10000秒,所有的虫洞都不大于万秒的时间回溯。

Input

第1行:一个整数F表示接下来会有F个农场说明。 每个农场第一行:分别是三个空格隔开的整数:N,M和W 第2行到M+1行:三个空格分开的数字(S,E,T)描述,分别为:需要T秒走过S和E之间的双向路径。两个区域可能由一个以上的路径来连接。 第M +2到M+ W+1行:三个空格分开的数字(S,E,T)描述虫洞,描述单向路径,S到E且回溯T秒。

Output

F行,每行代表一个农场 每个农场单独的一行,” YES”表示能满足要求,”NO”表示不能满足要求。

Sample Input

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

Sample Output

NO
YES
#include<iostream>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
struct edge
{
    int a,b,val;
}zu[5500];
int dis[505],N;
bool bellman(int index)
{
    int cnt=0,flag=true;
    while(cnt<=N-1&&flag){
        flag=false;
        for(int i=0;i<index;i++){
            if(dis[zu[i].b]>dis[zu[i].a]+zu[i].val){
                dis[zu[i].b]=dis[zu[i].a]+zu[i].val;
                flag=true;
            }
        }
        cnt++;
    }
    return flag;
}
int main()
{
    int t;
    cin>>t;
    while(t--){
        int n,m,a,b,val,index=0;
        cin>>N>>n>>m;
        memset(dis,inf,sizeof(dis));
        dis[1]=0;
        for(int i=0;i<n;i++){
            cin>>a>>b>>val;
            zu[index].a=a;
            zu[index].b=b;
            zu[index++].val=val;
            zu[index].a=b;
            zu[index].b=a;
            zu[index++].val=val;
        }
        for(int i=0;i<m;i++){
            cin>>a>>b>>val;
            zu[index].a=a;
            zu[index].b=b;
            zu[index++].val=-val;
        }
        cout<<(bellman(index)?"YES":"NO")<<endl;
    }
    return 0;
}

 

SPFA算法:

优化后的Bellman_Ford,时间复杂度较低,存在负权时也可以用

例题:Frogger   POJ 2253

湖中有n块石头,编号从1到n,有两只青蛙,Bob在1号石头上,Alice在2号石头上,Bob想去看望Alice,但由于水很脏,他想避免游泳,于是跳着去找她。但是Alice的石头超出了他的跳跃范围。因此,Bob使用其他石头作为中间站,通过一系列的小跳跃到达她。两块石头之间的青蛙距离被定义为两块石头之间所有可能路径上的最小必要跳跃距离,某条路径的必要跳跃距离即这条路径中单次跳跃的最远跳跃距离。你的工作是计算Alice和Bob石头之间的青蛙距离。

Input

多实例输入 
先输入一个整数n表示石头数量,当n等于0时结束。
接下来2-n+1行依次给出编号为1到n的石头的坐标xi , yi。
2 <= n <= 200 
0 <= xi , yi <= 1000

Output

先输出"Scenario #x", x代表样例序号。
接下来一行输出"Frog Distance = y", y代表你得到的答案。 
每个样例后输出一个空行。
(ps:wa有可能是精度问题,g++不对可以用c++尝试,都不对就是代码问题)

Sample Input

2
0 0
3 4

3
17 4
19 4
18 5

0

Sample Output

Scenario #1
Frog Distance = 5.000

Scenario #2
Frog Distance = 1.414
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
#include<iomanip>
using namespace std;
const int inf=0x3f3f3f3f;
int mark[205],n;
double dis[205];
struct edge
{
    int point2;
    double val;
};
vector<edge> intvector[205];
struct point
{
    int x,y;
}ZuP[205];
double bellman(int Begin,int End)
{
    queue<int> tp;
    for(int i=1;i<=n;i++){
        if(i==Begin)
            dis[i]=0;
        else
            dis[i]=inf;
    }
    mark[Begin]=1;
    tp.push(Begin);
    while(!tp.empty()){
        int point1=tp.front();
        tp.pop();
        mark[point1]=0;
        for(vector<edge>::iterator iter=intvector[point1].begin();iter!=intvector[point1].end();iter++){
            if(dis[iter->point2]>max(dis[point1],iter->val)){
                dis[iter->point2]=max(dis[point1],iter->val);
                if(mark[iter->point2]==0){
                    mark[iter->point2]=1;
                    tp.push(iter->point2);
                }
            }
        }
    }

    return dis[End];
}
double DIS(int a,int b)
{
    return sqrt(pow(ZuP[a].x-ZuP[b].x,2)+pow(ZuP[a].y-ZuP[b].y,2));
}
int main()
{
    int cnt=1;
    while(cin>>n){
        if(n==0)
            break;
        for(int i=1;i<=n;i++)
            intvector[i].clear();
        edge e;
        for(int i=1;i<=n;i++)
            cin>>ZuP[i].x>>ZuP[i].y;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                e.point2=i;
                e.val=DIS(i,j);
                intvector[j].push_back(e);
                e.point2=j;
                intvector[i].push_back(e);
            }
        }
        cout<<"Scenario #"<<cnt++<<'\n'<<"Frog Distance = "<<fixed<<setprecision(3)<<bellman(1,2)<<"\n\n";
    }
    return 0;
}

Dijkstra算法(优先队列优化):

时间复杂度低,但无法处理负权

例题: Subway    POJ 2502

一个人从家要到学校去,途中有许多车站,所以有步行和做地铁两种方式,其速度分别是10km/h 和40km/h。输入的规则是第一行输入的是x1,y1,x2,y2,分别代表家的坐标和学校的坐标。以后输入的是车站的坐标,数目不超过200,相邻的两个站点可以坐地铁,其他的需要步行。问到达学校的最短时间是多少?(因为不知道输入的数据有多少,所以用while(scanf()!=EOF)。其他的就没有什么要注意的了,建图很重要。)

Input

输入包括家和学校的x,y坐标,其次是几条地铁线的规格。每条地铁线路由线路上每个站点的非负整数x,y坐标组成,每条线至少有两个停靠点。每条地铁线的末端后跟虚拟坐标对-1 -1。该市共有200个地铁站。

Output

输出是上学所需的时间,四舍五入到最近的分钟,采取最快的路线。

Sample Input

0 0 10000 1000
0 200 5000 200 7000 200 -1 -1 
2000 600 5000 600 10000 600 -1 -1

Sample Output

21
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<string>
#include<queue>
#include<functional>
using namespace std;
const int num=205;
const int nume=205*205*2;
const int inf=0x3f3f3f3f;
typedef pair<double,int> pa;
struct point
{
    double x,y;
}zup[num];
struct edge
{
    int to,next;
    double weight;
}zue[nume];
int head[num],mark[num];
double dis[num];
int headindex;
void Insert(int a,int b,double weight)
{
    zue[headindex].to=b;
    zue[headindex].weight=weight;
    zue[headindex].next=head[a];
    head[a]=headindex++;
}
double DIS(int a,int b)
{
    return sqrt(pow(zup[a].x-zup[b].x,2)+pow(zup[a].y-zup[b].y,2));
}
double dijkstra_pq(int Begin,int End)
{
    for(int i=1;i<=num;i++)
        dis[i]=inf;
    dis[Begin]=0;
    priority_queue<pa,vector<pa>,greater<pa> > pq;
    pq.push(pa(dis[Begin],Begin));
    while(!pq.empty()){
        pa k=pq.top();
        pq.pop();
        int minp=k.second;
        if(minp==End)
            return dis[End];
        if(mark[minp])
            continue;
        mark[minp]=true;
        for(int index=head[minp];index!=-1;index=zue[index].next){
            if(dis[zue[index].to]>dis[minp]+zue[index].weight){
                dis[zue[index].to]=dis[minp]+zue[index].weight;
                pq.push(pa(dis[zue[index].to],zue[index].to));
            }
        }
    }
    return dis[End];
}
int main()
{
    int indexe=1,indexp=3;
    memset(head,-1,sizeof(head));
    scanf("%lf %lf %lf %lf",&zup[1].x,&zup[1].y,&zup[2].x,&zup[2].y);
    double x,y,bex=-1,bey=-1;
    while(scanf("%lf %lf",&x,&y)!=-1){
        if(x==-1&&y==-1){
            bex=-1,bey=-1;
            continue;
        }
        zup[indexp].x=x;
        zup[indexp++].y=y;
        if(bex!=-1&&bey!=-1){
            Insert(indexp-1,indexp-2,DIS(indexp-1,indexp-2)/4000*6);
            Insert(indexp-2,indexp-1,DIS(indexp-1,indexp-2)/4000*6);
        }
        bex=x,bey=y;
    }
    for(int i=1;i<indexp;i++){
        for(int j=1;j<indexp;j++){
            if(i==j)
                continue;
            Insert(i,j,DIS(i,j)/1000*6);
        }
    }
    printf("%.0lf\n",dijkstra_pq(1,2));
    return 0;
}

总结:

(1)当权值为非负时,用Dijkstra。

(2)当权值有负值,且没有负环,则用SPFA,SPFA能检测负环(一个节点进入队列n次),但是不能输出负环。

(3)当权值有负值,而且可能存在负环,则用BellmanFord,能够检测并输出负环。

猜你喜欢

转载自blog.csdn.net/Chter0/article/details/89025844
今日推荐