题目
https://www.patest.cn/contests/pat-a-practise/1030
题意:给出每座城市之间高速公路的长度和花费,求从给定起点到终点的最短路径并输出,若有多条最短路径,记录花费最小的那条。
解题思路
本题是单源最短路径的简单变形。不仅要考虑距离,还要考虑花费。
关于单源最短路径,详见之前的一篇博客:PAT 1003 Emergency(单源最短路径+Dijkstra)
对算法的修改如下:
- 在维护dist数组的同时,还要维护cost数组,其意义与dist基本相同。
松弛时要考虑的情况:
- 通过pos到k的距离更短
- 通过pos到k的距离相等时,花费更小。
出现上述两种情况时,要同时更新dist和cost数组。
AC代码
优先队列优化过的Dijkstra
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef pair<int, int> P; ///first是距离,second是编号
const int maxn = 505, INF = 1<<27;
int graph[maxn][maxn], weight[maxn][maxn];
int dist[maxn], cost[maxn]; //dist、cost表示当前点距源点最短距离(花费)
bool visit[maxn];
int pre[maxn]; //记录前驱顶点
int n, m, src, dest;
void init() //用fill初始化,比memset功能更强,注意格式
{
fill(graph[0], graph[0]+maxn*maxn, INF); //二维数组形式
fill(weight[0], weight[0]+maxn*maxn, INF); //一维数组形式
fill(visit, visit+maxn, false); //布尔形式
fill(dist, dist+maxn, INF);
fill(cost, cost+maxn, INF);
}
void Dijkstra(int s) ///优先队列优化的dijkstra算法
{
dist[s] = cost[s] = 0;
priority_queue<P, vector<P>, greater<P> > q; //按照pair的第一个值升序
q.push(P(0, s));
while(!q.empty())
{
P out = q.top();
q.pop();
int pos = out.second; //顶点编号
if (visit[pos])
continue;
//从pos开始松弛
visit[pos] = true; //标记访问
for (int i = 0; i < n; ++i)
{
if (!visit[i] && dist[pos] + graph[pos][i] <= dist[i]) //通过pos到i路径可能会更短
{
if ( dist[pos] + graph[pos][i] < dist[i] ||
(dist[pos] + graph[pos][i] == dist[i] && cost[pos] + weight[pos][i] < cost[i]) ) //路径更短或路径长度相等但花费更少
{
pre[i] = pos; //更新前驱顶点
dist[i] = dist[pos] + graph[pos][i];
cost[i] = cost[pos] + weight[pos][i];
q.push(P(dist[i], i)); //加入更新后的路径值和顶点编号
}
}
}
}
}
int main()
{
scanf("%d %d %d %d", &n, &m, &src, &dest);
init(); //初始化
int from, to, dis, cos;
for (int i = 0; i < m; ++i)
{
scanf("%d %d %d %d", &from, &to, &dis, &cos);
graph[from][to] = graph[to][from] = dis;
weight[from][to] = weight[to][from] = cos;
}
Dijkstra(src);
//输出路径
int path[maxn], cnt = 0, tmp = dest;
while (tmp != src)
{
path[cnt++] = tmp;
tmp = pre[tmp];
}
path[cnt++] = src;
for (int i = cnt-1; i >= 0; --i)
printf("%d ", path[i]);
printf("%d %d\n", dist[dest], cost[dest]);
return 0;
}
小技巧:
STL中的泛型函数fill功能比memset更强大,memset只能对字节赋值,而fill则是任何值都可以,调用规则:
fill(first,last,val)
,其中first 为容器的首迭代器,last为容器的末迭代器,last为将要替换的值,注意二维数组a[x][y]的首尾迭代器要写a[0]而不是a。
未优化的Dijkstra
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 505, INF = 0X3f3f3f3f;
int map[maxn][maxn], weight[maxn][maxn];
int dist[maxn], cost[maxn]; //维护dist同时维护cost
int pre[maxn]; //记录前驱结点
bool visited[maxn];
int n;
void init()
{
for (int i = 0; i<n; ++i)
{
for (int j = 0; j<n; ++j)
{
map[i][j] = (i==j)? 0:INF;
weight[i][j] = (i==j)? 0:INF;
}
visited[i] = false;
pre[i] = -1;
}
}
void dijkstra(int st)
{
for (int i = 0; i<n; ++i)
{
dist[i] = map[st][i];
cost[i] = weight[st][i]; //更新cost
if (dist[i] != INF)
pre[i] = st;
}
visited[st] = true;
for (int i = 0; i < n-1; ++i)
{
int pos = -1, minn = INF;
//找到dist当前最小值
for (int j = 0; j < n; ++j)
{
if (!visited[j] && dist[j] < minn)
{
minn = dist[j];
pos = j;
}
}
visited[pos] = true; //标记pos被访问过
//松弛操作
for (int k = 0; k < n; ++k)
{
if (!visited[k] && dist[pos] + map[pos][k] <= dist[k])
{
//经过pos到k的距离更短,或距离相同代价更小
if (dist[pos] + map[pos][k] < dist[k] || ((dist[pos] + map[pos][k] == dist[k]) && cost[pos] + weight[pos][k] < cost[k]))
{
dist[k] = dist[pos] + map[pos][k]; //更新dist
cost[k] = cost[pos] + weight[pos][k]; //更新cost
pre[k] = pos;
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
int m, start, dest;
cin >> n >> m >> start >> dest;
init();
int t1, t2, d, w;
for (int i = 0; i < m; ++i)
{
cin >> t1 >> t2 >> d >> w;
map[t1][t2] = map[t2][t1] = d;
weight[t1][t2] = weight[t2][t1] = w; //记录代价
}
dijkstra(start);
int along[maxn], cnt = 0;
int t = dest;
while (t != start) //记录所有前驱结点
{
along[cnt++] = t;
t = pre[t];
}
along[cnt++] = start; //加入起点
for (int i = cnt-1; i >= 0; --i)
cout << along[i] << ' ';
cout << dist[dest] << ' ' << cost[dest] << endl;
return 0;
}