算法训练 最短路
时间限制: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 -1
2 3 -1
3 1 2
样例输出
-1
-2
-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 0x3fffffff
using 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 0x3fffffff
using 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;
}