[A*] aw178. 第K短路(A*+bfs最小步数模型+好题)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[A*] aw178. 第K短路(A*+bfs最小步数模型+好题)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:178. 第K短路
相关:
2. 题目解析
A ∗ A^* A∗ 算法先去看 [A*] aw179. 八数码(A*+bfs最小步数模型+模板题) 。
本题有一个坑点需要注意,每条最短路至少包含一条边,当起点与终点相同时,需要将 K ++
。
需要求解从起点到终点的第 k
短路,则我们每次都需要枚举该点的所有出边,将所有的边全部枚举到才能选出最短的 k
条边。所以解空间就非常大,
A
∗
A^*
A∗ 算法就能派上用场了。
估价函数设置为每个点到终点的最短距离, 这样不论什么情况下,该点到终点的估计距离一定小于等于真实距离。在实际操作中,只需要将图中边的方向反过来,以终点作为起点,做一遍 dijkstra()
即可得到终点到每个点的最短路,以它来作为估价值。
由于每个点都会出队很多次,且但终点第一次出队时就找到了起点到终点的最短路,那么当终点第二次、第三次出队,是否是第二短路、第三短路呢?
- 其实证明方式与终点第一次出队为最短路一样,都是反证法。
- 如果不为第二短路、第三短路,那么第二短路、第三短路上的点一定还在队列中,其到终点的距离一定更短,一定更先出队。发生矛盾。
这里新加了一个剪枝,记录每个点出队时的次数。在有解时,要取得第 K
短路,每个点最多只能走不超过 K
次,如果超过 K
次的话,这个点每次都能走到终点,那么我们就有 >K
条路可以到达终点, 所以这时到达的终点一定不是第 K
短路。
很优秀就完事了
时间复杂度: O ( ) O() O()
空间复杂度: O ( ) O() O()
这个剪枝需要注意下,cnt
数组记录出队点的次数,是个优秀的剪枝,不过没太懂是啥意思。
貌似是用来判断无解情况的,可以先判断 if(d[s]==0x3f3f3f3f) return -1
说明不连通,直接输出 -1 即可。
下面的原版代码在起点终点不连通时,终点始终不会出队,cnt
一直不会增加,while
循环就是个死循环。但是修改过之后,拿了 K
作为每个点入队列次数的限制,即如果出队 K
次之后就不能再入队了之后队列就会被清空, 这个貌似是个剪枝,但是思考并不自然,也是能够处理无解情况,但显然不够优雅。
/*
TLE 样例
3 2
1 2 1
2 1 1
1 3 1000
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 1010, M = 2e5 + 5; // 建正向、反向边,边的空间需要两倍
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N]; // 记录每个点到达的终点
bool st[N];
void add(int h[], int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// 堆优化dijkstra() 求终点到起点反相边的最短距离
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, T});
dist[T] = 0;
while (heap.size()) {
auto t = heap.top(); heap.pop();
int v = t.first, idx = t.second;
if (st[idx]) continue;
st[idx] = true;
for (int i = rh[idx]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[idx] + w[i]) {
dist[j] = dist[idx] + w[i];
heap.push({dist[j], j});
}
}
}
}
int astar() {
// if(d[s]==0x3f3f3f3f) return -1; // 应该加上无解情况的判断,代替这个cnt剪枝
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
heap.push({dist[S], {0, S}});
while (heap.size()) {
auto t = heap.top(); heap.pop();
int v = t.second.first, idx = t.second.second;
cnt[idx] ++ ; // 记录每个点到达的次数
if (cnt[T] == K) return v;
for (int i = h[idx]; ~i; i = ne[i]) {
int j = e[i];
if (cnt[j] < K) { // 新加的
// S---i v, i--j w[i] 估价 dist[j]
heap.push({v + w[i] + dist[j], {v + w[i], j}}); // 将临边全部加入队列
}
}
}
return -1;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 0; i < m; i ++ ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(h, a, b, c);
add(rh, b, a, c);
}
scanf("%d%d%d", &S, &T, &K);
if (S == T) K ++ ; // 至少包含一条边,
dijkstra();
printf("%d\\n", astar());
return 0;
}
TLE 版本:
// TLE 的版本
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 1010, M = 2e5 + 5; // 建正向、反向边,边的空间需要两倍
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
void add(h[], int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// 堆优化dijkstra() 求终点到起点反相边的最短距离
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, T});
dist[T] = 0;
while (heap.size()) {
auto t = heap.top(); heap.pop();
int v = t.first, idx = t.second;
if (st[idx]) continue;
st[idx] = true;
for (int i = rh[idx]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[idx] + w[i]) {
dist[j] = dist[idx] + w[i];
heap.push({dist[j], j});
}
}
}
}
int astra() {
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
heap.push({dist[S], {0, S}});
int cnt = 0; // 记录次数
while (heap.size()) {
auto t = heap.top(); heap.pop();
int v = t.secod.first, idx = t.second.second;
if (idx == T) cnt ++ ;
if (cnt == K) return v;
for (int i = h[idx]; ~i; i = ne[i]) {
int j = e[i];
// S---i v, i--j w[i] 估价 dist[j]
heap.push({v + w[i] + dist[j], {v + w[i], j}}); // 将临边全部加入队列
}
}
return -1;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 0; i < m; i ++ ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(h, a, b, c);
add(rh, b, a, c);
}
scanf("%d%d%d", &S, &T, &K);
if (S == T) K ++ ; // 至少包含一条边,
dijkstra();
printf("%d\\n", astar());
return 0;
}
以上是关于[A*] aw178. 第K短路(A*+bfs最小步数模型+好题)的主要内容,如果未能解决你的问题,请参考以下文章