[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值