算法训练 最短路

来源:互联网 发布:mac. wps 编辑:程序博客网 时间:2024/04/28 08:09


算法训练 最短路  
时间限制:1.0s   内存限制:256.0MB
      
问题描述

给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。

输入格式

第一行两个整数n, m。

接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。

输出格式
共n-1行,第i行表示1号点到i+1号点的最短路。
样例输入
3 3
1 2 -1
2 3 -1
3 1 2
样例输出
-1
-2
数据规模与约定

对于10%的数据,n = 2,m = 2。

对于30%的数据,n <= 5,m <= 10。

对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。


思路分析:首先要知道最短路常用的几种算法Dijkstra、Floyd、Bellman-Ford、SPFA、BFS。观察数据范围,顶点数最多有2W,用邻接矩阵存储图会内存超限,导致提交显示运行错误(其实就是内存炸了)。所以要用邻接表存储。因为图中有负权,Dijkstra不能用;因为图是带权的,所以BFS不能用;剩下的三个,好像都可以AC,Floyd时间复杂度较高,是立方级的,不建议用;Bellman-Ford时间复杂度也较高,有待考虑;最为稳妥的做法是用SPFA,SPFA就是利用队列优化过的Bellman-Ford。


先上一个Dijkstra的代码,可以通过80%数据,其余超时,利用小根堆优化理论应该可以AC(我有点疑问...不是Dijkstra不能处理带负权的图吗?还是不能处理带负环的图?)

#include <cstdio>#include <string>#include <vector>#define MAX 20000 + 10#define INF 0x3fffffffusing namespace std;typedef struct {    int v;    int l;} Edge;vector<Edge> MGraph[MAX];int dist[MAX];int visit[MAX];void Dijkstra( int n ) {    fill( dist, dist + MAX, INF );    dist[0] = 0;    for( int i = 0; i < n; i++ ) {        // 这一部分是找最小值的过程,可以用小根堆优化                int u = -1, MIN = INF;        for( int j = 0; j < n; j++ ) {            if( !visit[j] && dist[j] < MIN ) {                MIN = dist[j];                u = j;            }        }                //                if( u == -1 ) return;        visit[u] = 1;        for( int j = 0; j < MGraph[u].size(); j++ ) {            int v = MGraph[u][j].v;            if( !visit[v] ) {                if( MGraph[u][j].l + dist[u] < dist[v] ) {                    dist[v] = dist[u] + MGraph[u][j].l;                }            }        }    }}int main() {    int n, m;    scanf( "%d%d", &n, &m );    int a, b, l;    for( int i = 0; i < m; i++ ) {        scanf( "%d%d%d", &a, &b, &l );        a--;        b--;        Edge e;        e.v = b;        e.l = l;        MGraph[a].push_back( e );    }    Dijkstra( n );    for( int i = 1; i < n; i++ ) {        printf( "%d\n", dist[i] );    }    return 0;}


下面这个是AC的SPFA算法,时间大约是400MS;我试了下用优先队列,需要600MS

#include <cstdio>#include <string>#include <vector>#include <queue>#include <string.h>#define MAX 20000 + 10#define INF 0x3fffffffusing namespace std;typedef struct {    int v;    int l;} Edge;vector<Edge> MGraph[MAX];int dist[MAX];int visit[MAX];int inq[MAX];int num[MAX];bool SPFA( int s, int n ) {    // 初始化部分    memset( inq, false, sizeof( inq ) );    memset( num, 0, sizeof( num ) );    fill( dist, dist + MAX, INF );    // 源点入队部分    queue<int> Q;    Q.push( s );    // 源点入队    inq[s] = true;  // 源点已入队    num[s]++;       // 源点入队次数加1    dist[s] = 0;    // 主体部分    while( !Q.empty() ) {        int u = Q.front();  // 队首顶点编号为u        Q.pop();        inq[u] = false;     // 设置u不在队列中        // 遍历u的所有邻接边v        for( int j = 0; j < MGraph[u].size(); j++ ) {            int v = MGraph[u][j].v;            int dis = MGraph[u][j].l;            // 松弛操作            if( dist[u] + dis < dist[v] ) {                dist[v] = dist[u] + dis;                if( !inq[v] ) { // 如果v不在队列中                    Q.push( v );    // v入队                    inq[v] = true;  // 设置v为在队列中                    num[v]++;       // v的入队次数加1                    if( num[v] >= n ) return false; // 有可达负环                }            }        }    }    return true;    // 无可达负环}int main() {    int n, m;    scanf( "%d%d", &n, &m );    int a, b, l;    for( int i = 0; i < m; i++ ) {        scanf( "%d%d%d", &a, &b, &l );        a--;        b--;        Edge e;        e.v = b;        e.l = l;        MGraph[a].push_back( e );    }    SPFA( 0, n );    for( int i = 1; i < n; i++ ) {        printf( "%d\n", dist[i] );    }    return 0;}


1 0