HDU2680Choose the best route(SPFA)

题目的意思:

                 第一组输入3个数,n, m and s,(n<1000,m<20000,1=<s<=n) ,其中n为结点个数,m为边数,s为终点。

                 之后有m条输入,p , q , t (0<t<=1000),表示从点p到点q的时间为t.

                 最后输入一个数n,表示起点有n种可能。

这道题目可以用Dijkstra,也是比较经典的题目,换换新的方式,改用SPFA试试。

注意:这个图是单向的。

Input
There are several test cases.
Each case begins with three integers n, m and s,(n<1000,m<20000,1=<s<=n) n stands for the number of bus stations in this city and m stands for the number of directed(定向) ways between bus stations .(Maybe there are several ways between two bus stations .) s stands for the bus station that near Kiki’s friend’s home.
Then follow m lines ,each line contains three integers p , q , t (0<t<=1000). means from station p to station q there is a way and it will costs t minutes .
Then a line with an integer w(0<w<n), means the number of stations Kiki can take at the beginning. Then follows w integers stands for these stations.
这上面有一句话:directed(定向) ways between bus stations

SPFA:

          建议看SPFA前先看看Dijkstra和Bellman-Ford这两个最短路算法。

SPFA的思路比较简单,网上的说法也比较统一,NOCOW和百度百科上都有。这里在网上找到讲的比较通俗易懂的:

 

SPFA(Shortest Path Faster Algorithm)
是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,
并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。

它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。

SPFA 在形式上和BFS非常类似,不同的是BFS中一个点出了队列就不可能重新进入队列,但是SPFA中
一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本
身被改进,于是再次用来改进其它的点,这样反复迭代下去。

判断有无负环:如果某个点进入队列的次数超过V次则存在负环(SPFA无法处理带负环的图)。(这个是SPFA的NB之处)

 

思路:

       这道题一开始我是想用常规的方法,就是将后面可能出现的起点一个一个慢慢调用一次,然后比较求出最小路径。但是发现这样的方法会TLE,所以后来改为从终点往起点处扫。就是存边的时候头尾倒过来存,然后只调用一次SPFA,就可以保存所有起点的最短路径。改进后的程序只调用了SPFA函数一次。所以速度会快了很多。

ps:

1、单向还是双向图,考虑清楚
2、注意那个maxInt这个值超不超,够不够
3、注意是否两点间有多条路径
4、分清变量是double型的还是int型的
5、注意主函数中初始化map[][]中的点边不要搞错(注意所有初始化,正确命名好变量)

#include<iostream> 
using namespace std; 
 
const int maxNum = 1010
const int maxInt = 999999999
 
int map[maxNum][maxNum]; 
int dis[maxNum]; 
char vst[maxNum]; 
 
int nodeNum, edgeNum, start, result; 
 
void SPFA(int start) 

    int i, pri, rear, p, Q[20*maxNum]; //注意队列这里的大小,因为每个点都能够入队在10次以内 
    memset(vst, 0sizeof(vst)); 
 
    for( i = 1; i <= maxNum; i++) 
        Q[i] = 0
 
    for(i = 1; i <= nodeNum; i++) 
        dis[i] = maxInt; 
 
    dis[start] = 0
    vst[start] = 1
    Q[0] = start; 
    pri = 0
    rear = 1
 
    while(pri < rear) 
    { 
        p = Q[pri]; 
        for(i = 1; i <= nodeNum; i++) 
        { 
            if(dis[p] + map[p][i] < dis[i]) 
            { 
                dis[i] = dis[p] + map[p][i]; 
                if(!vst[i]) 
                { 
                    Q[rear++] = i; 
                    vst[i] = 1
                } 
            } 
        } 
        vst[p] = 0
        pri++; 
    } 
    return ; 

 
int main(void

    int i, s, e, w, j, min, nn, ans[maxNum]; 
    while(scanf("%d%d%d", &nodeNum, &edgeNum, &result) == 3
    { 
        for(i = 1; i <= nodeNum; i++) 
            for(j = 1; j <= nodeNum; j++) 
            { 
                map[i][j] = maxInt; 
            } 
 
        for(i = 1; i <= edgeNum; i++) 
        { 
            scanf("%d%d%d", &s, &e, &w); 
            if(map[e][s] > w) 
                map[e][s] = w; //注意这个图是单向的,这里有个小技巧,头尾颠倒处理。 
        } 
        SPFA(result);//把这个放外面,头尾颠倒,调用一次函数就可以了。如果按照一般的想法放在循环内部调用,那么会TLE 
        scanf("%d", &nn); 
        min = maxInt; 
        for(i = 0; i < nn; i++) 
        { 
            scanf("%d", &start); 
            if(min > dis[start]) 
                min = dis[start]; 
        } 
        if(min == maxInt) 
        { 
            printf("-1\n"); 
        } 
        else 
        { 
            printf("%d\n", min); 
        } 
    } 
    return 0

原文地址:https://www.cnblogs.com/cchun/p/2520131.html