【ACWing】178. 第K短路

题目地址:

https://www.acwing.com/problem/content/180/

给定一张 N N N个点(编号 1 , 2 … N 1,2…N 1,2N), M M M条边的有向图,求从起点 S S S到终点 T T T的第 K K K短路的长度,路径允许重复经过点或边。注意:每条最短路中至少要包含一条边。

输入格式:
第一行包含两个整数 N N N M M M。接下来 M M M行,每行包含三个整数 A A A B B B L L L,表示点 A A A与点 B B B之间存在有向边,且边长为 L L L。最后一行包含三个整数 S S S T T T K K K,分别表示起点 S S S、终点 T T T和第 K K K短路。

输出格式:
输出占一行,包含一个整数,表示第 K K K短路的长度,如果第 K K K短路不存在,则输出 − 1 −1 1

数据范围:
1 ≤ S , T ≤ N ≤ 1000 1≤S,T≤N≤1000 1S,TN1000
0 ≤ M ≤ 105 0≤M≤105 0M105
1 ≤ K ≤ 1000 1≤K≤1000 1K1000
1 ≤ L ≤ 100 1≤L≤100 1L100

这里需要注意两点:
1、每条最短路中至少要含一边,所以如果起点终点重合的话,不含边的那条长度为 0 0 0的路径是不能算进去的。
2、着重要注意排除掉第 K K K短路不存在的情况。下面的算法内可以解决这个问题。

用A*算法。思路参考https://blog.csdn.net/qq_46105170/article/details/114877098,那里证明了当终点第一次出堆的时候,数对的第一维就是最短路距离。本题要求第 K K K短路,那么就可以猜测终点如果是第 K K K次出堆,第一维存的就是第 K K K短路。这个结论是正确的,证明如下:
数学归纳法。当 K = 1 K=1 K=1结论成立。当 K < n K<n K<n的时候假设结论也成立,当 K = n K=n K=n的时候, 这里先要排除一下终点第 K − 1 K-1 K1次出堆之后再也没出堆过的情况,此时说明不存在第 K K K短路;如果终点第 K K K次出堆,但是堆空,则说明第 K K K短路的走法是唯一的,那显然结论成立;如果终点第 K K K次出堆,且堆不空,那么我们证明此时的 d d d里存的一定是第 K K K短路的长度,如果不然,那么其一定大于第 K K K短路长度,而堆里一定存在真实第 K K K短路的某个顶点还没出堆,其 d + h d+h d+h的值是小于等于真实第 K K K短路的长度的,那么它应该先出堆,这就矛盾了。

对于这题来说,如果起点终点重合,那么要求的第 K K K短路实际上要求成是第 K + 1 K+1 K+1短路,即要包含边数为 0 0 0的那条。此外就是启发函数 h ( x ) h(x) h(x)的选取,我们可以直接取成 x x x到终点的实际最短路距离,这个距离可以通过建反向图然后用Dijkstra算法来求。然后再按正向图,从起点做A*算法。当终点第 K K K次出堆的时候,第一维就是第 K K K短路距离。但是,由于可能不存在第 K K K短路,我们需要先判断一下起点是否能到达终点,并且在扩展的时候,要将那些与终点不连通的点排除掉。这样如果第 K K K短路不存在,就会使得堆提前变空(因为堆里的点一定都是能走到终点的点),这样就能识别出来了。

代码如下:

#include <iostream>
#include <cstring>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
typedef pair<int, PII> PIII;

const int N = 1010, M = 2 * 100000 + 10;
int n, m, S, T, K;
// h存正向边,rh存反向边
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N];
// Dijkstra算法里的st数组,用于标记某个点最短路是否已经被算出来过
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++;
}

void dijkstra() {
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    heap.push({0, T});
    dist[T] = 0;

    while (!heap.empty()) {
        auto t = heap.top();
        heap.pop();

        int v = t.y;
        if (st[v]) continue;
        st[v] = true;

        for (int i = rh[v]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[v] + w[i]) {
                dist[j] = dist[v] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

int astar() {
	// 如果起点到不了终点,则说明不存在路径,直接返回-1
    if (dist[S] == 0x3f3f3f3f) return -1;
    
    priority_queue<PIII, vector<PIII>, greater<PIII> > heap;
    heap.push({dist[S], {0, S}});

    int cnt = 0;
    while (!heap.empty()) {
        auto t = heap.top();
        heap.pop();

        int v = t.y.y, dis = t.y.x;
        if (v == T) cnt++;
        // 如果是第K次出堆,则返回其d值
        if (cnt == K) return dis;

        for (int i = h[v]; ~i; i = ne[i]) {
            int j = e[i];
            // 略过走不到终点的点
            if (dist[j] == 0x3f3f3f3f) continue;
            heap.push({dis + w[i] + dist[j], {dis + 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;
}

时间复杂度 O ( K E log ⁡ V ) O(KE\log V) O(KElogV),空间 O ( K V ) O(KV) O(KV)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值