题目描述:
给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。
输入:
输入n,m,点的编号是1~n,然后是m行,每行4个数 a,b,d,p,表示a和b之间有一条边,且其长度为d,花费为p。最后一行是两个数 s,t;起点s,终点t。n和m为0时输入结束。
(
1
<
n
≤
1000
,
0
<
m
<
100000
,
s
≠
t
1<n \le 1000, 0<m<100000, s \neq t
1<n≤1000,0<m<100000,s=t)
输出:
输出 一行有两个数, 最短距离及其花费。
样例输入:
3 2
1 2 5 6
2 3 4 5
1 3
0 0
样例输出:
9 11
提示:
拿到这题,第一印象就是最短路径问题(题目写得清清楚楚)。当然,你可以直接套用最短路径的解法。不过这里我要给大家介绍另一种解法,同样是图论里的算法——深度优先搜索。
从起点开始进行深度优先搜索,当搜索的结点到达终点时查看路径总长度与花费是否比已经得到的最短路径和最小花费小,如果要小的话就使用当前的路径和花费取代最短路径和最小花费。
当遍历到终点时是否就结束了?非也,还需要从其他路径遍历,判断其他路径是否又更短花费更小的。这里就需要用到回溯了。所谓回溯,就是还需要往回走来走其他的路径。那么会不会进入循环状态呢?对遍历过的结点进行标记就能够杜绝循环。当然需要在回溯时取消之前的标记。
实现代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1000 + 10;
const int INF = 0x3fffffff;
int n, G[maxn][maxn];
bool vis[maxn];
int d[maxn];
int c[maxn];
int cost[maxn][maxn];
void Dijkstra(int s, int e) {
fill(d, d + maxn, INF);
fill(vis, vis + maxn, false);
memset(c, 0, sizeof(c));
d[s] = 0;
c[s] = 0;
// 找s到未访问结点的最小距离
for(int i = 0; i < n; i++) {
int u = -1, MIN = INF;
for(int j = 1; j <= n; j++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
// 此if判断是用于剪枝,当得到所求时就退出,不在继续计算。
if(u == e) {
return;
}
if(u == -1) {
return;
}
vis[u] = true;
for(int v = 1; v <= n; v++) {
if(G[u][v] != INF && vis[v] == false) {
// 距离更近,就更新距离,权值
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
c[v] = c[u] + cost[u][v];
// 距离一样,不用更新距离
} else if(d[u] + G[u][v] == d[v]) {
// 如果花费更小,则更新权值
if(c[u] + cost[u][v] < c[v]) {
c[v] = c[u] + cost[u][v];
}
}
}
}
}
}
int main() {
int m, s, t;
int c1, c2;
while(scanf("%d%d", &n, &m) != EOF) {
if(n == 0 && m == 0) {
break;
}
fill(G[0], G[0] + maxn * maxn, INF);
for(int i = 0; i < m; i++) {
scanf("%d%d", &c1, &c2);
scanf("%d%d", &G[c1][c2], &cost[c1][c2]);
G[c2][c1] = G[c1][c2];
cost[c2][c1] = cost[c1][c2];
}
scanf("%d%d", &s, &t);
Dijkstra(s, t);
printf("%d %d\n", d[t], c[t]);
}
return 0;
}