资源限制
时间限制: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算法要求边的权值非负,因此不能使用;题目中n的最大值为20000,但Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2),一定会超时(n小于500时再考虑Floyd-Warshall算法)。想到Bellman-ford算法适用于单源最短路径,图中边的权重可为负数即负权边,但不可以出现负权环,因此本题使用Bellman-ford算法。
这里还需要解释一下负边权和负权环的概念:
负权边:权重为负数的边。
负权环:源点到源点的一个环,环上权重和为负数。
算法描述:
1.设立五个一维数组:存放从第1个节点到第i个节点的最短路径dist[20005],临时数组tmp[20005],边起点数组u[200009],边终点数组v[200009],边权值数组len[200009]。
int u[200009],v[200009],len[200009];
int dist[20005],tmp[20005];
2.遍历所有的边,来改变每个源点的最短距离(核心思想)。
for(int i=1;i<=n;i++)
{
//flag = 0;
//for(int j=1;j<=n;j++)
//{
// tmp[j] = dist[j];
//}
for(int k=1;k<=m;k++)
{
if(dist[v[k]]>dist[u[k]]+len[k])
{
dist[v[k]] = dist[u[k]]+len[k];
}
}
}
考虑到时间复杂度为O(n2),当n取值偏大时可能会出现超时的情况,因此需要用一个临时数组tmp来储存每轮结束后的最短路径数,用一个标记flag来监控该数据是否发生改变,如果出现变化则继续,反之则退出循环,表示已经找到全部的最短路径。
for(int h=1;h<=n;h++)
{
if(tmp[h]!=dist[h])
{
flag = 1;
break;
}
}
if(flag==0)
{
break;
}
代码:
#include<iostream>
#include<cstring>
using namespace std;
int u[200009],v[200009],len[200009];
int dist[20005],tmp[20005]; //临时数组
int main()
{
int n,m,flag; //标记
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>len[i];
}
memset(dist,1000000,sizeof dist); //所有路径初始化为最大值
dist[1] = 0; //本身距离为0
for(int i=1;i<=n;i++)
{
flag = 0;
for(int j=1;j<=n;j++)
{
tmp[j] = dist[j];
}
for(int k=1;k<=m;k++)
{
if(dist[v[k]]>dist[u[k]]+len[k])
{
dist[v[k]] = dist[u[k]]+len[k]; //计算最短路径
}
}
for(int h=1;h<=n;h++)
{
if(tmp[h]!=dist[h]) //只要发生改变就退出 ,说明仍然存在还未找出的最短路径
{
flag = 1;
break;
}
}
if(flag==0) //说明找到了所有的最短路径,没有必要再进行下去,结束循环
{
break;
}
}
for(int i=2;i<=n;i++)
{
cout<<dist[i]<<endl; //输出1号点到i+1号点的最短路径
}
return 0;
}