題目
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;
}