AcWing 178 第K短路


题目传送门
A星算法详解(个人认为最详细,最通俗易懂的一个版本)

二、代码实现

#include 

using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair PII;
typedef pair PIII;
#define x first
#define y second

const int N = 1010, M = 200010; //因为要建反向边,所以边数是2倍,20W+10
int n, m;                       //点数和边数
int S, T, K;                    //起点,终点,第K短
int h[N], rh[N];                //正向边的邻接表表头和反向边的邻接表表头
int e[M], w[M], ne[M], idx;     //数组模拟邻接表
int dist[N];                    //到每个点的最短距离
bool st[N];                     //每个点用没用过
int cnt[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
// 通过这个反向搜索的dijkstra,就可以计算出点T到每个其它点的最短距离,把这个最短距离dist[i]做为估价函数
void dijkstra() {
    priority_queue, greater> heap; // 优先队列,小根堆

    //小根堆是要求按到起点的距离进行由小到大自动排序的,现在反向搜索,当然距离是T的距离是0
    heap.push({0, T}); // 终点T是这里的起点 <距离,点编号>,小根堆,默认按PII的first进行排序,
    //即距离起点的长度由小到大排序

    //初始化距离
    memset(dist, 0x3f, sizeof dist);
    dist[T] = 0;

    while (heap.size()) {
        PII t = heap.top();
        heap.pop();

        int u = t.y;                         //要处理哪个点
        if (st[u]) continue;                 //如果这个点已经出过队列了
        st[u] = true;                        //标识已搜索
        for (int i = rh[u]; ~i; i = ne[i]) { //在反向图上遍历,rh[]数组
            int j = e[i];                    //点u有一条到点j的边
            if (dist[j] > dist[u] + w[i]) {  //如果能通过点u优化掉到点j的距离,就优化一下
                dist[j] = dist[u] + w[i];
                heap.push({dist[j], j}); //并且将点j和点距离出发点T的距离组成数对放入到优化队列中
            }
        }
    }
}

// A*算法
// A* 不用判重
int astar() {
    //小顶堆,对F值进行从小到大排序,每次取队首
    priority_queue, greater> heap;
    // 小顶堆内的元素:
    // PII的first: F = G + H
    // G = 从起点 A 移动到指定方格的移动代价,沿着到达该方格而生成的路径。
    // H = 从指定的方格移动到终点 B 的估算成本。
    // 在A*中,用来使用小顶堆排序的,其实是这个F,也就是实际距离累加和+估算值,按这个值来确定处理优先级
    heap.push({dist[S], {0, S}}); //在添加S节点时,由于S距离自己的距离是0,所以就可有一个H值,即dist[S]
    //后面数对PII的含义是:当前节点距离出发点的距离,当前节点号

    while (heap.size()) {
        PIII t = heap.top();
        heap.pop();
        // t.x在下面的代码中未使用,原因很简单,这个x本来也不是啥准确值,只是一个估值
        int distance = t.y.x; // u距离出发点的叠加距离
        int u = t.y.y;        // 下一个要扩展的点u
        cnt[u]++;             // 点u出队的次数++

        //如果此时的u就是终点T,前且已经出队k次 返回答案
        if (u == T && cnt[u] == K) return distance;

        for (int i = h[u]; ~i; i = ne[i]) { //枚举与u相连的所有出边
            int j = e[i];                   //找到节点j
            /*
            A*寻路的一个优化:
            如果走到一个中间点都cnt[j]>=K,则说明j已经出队k次了,且astar()并没有return distance,
            说明从j出发找不到第k短路(让终点出队k次),即继续让j入队的话依然无解,那么就没必要让j继续入队了
            */
            if (cnt[j] < K) {
                // distance + w[i] 累加长度
                // 真实值+估计值 排序
                heap.push({distance + w[i] + dist[j], {distance + w[i], j}});
            }
        }
    }
    // 终点没有被访问k次
    return -1;
}

int main() {
    cin >> n >> m;

    memset(h, -1, sizeof h);   //正向边表头
    memset(rh, -1, sizeof rh); //反向边表头

    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        add(h, a, b, c);  //建正边
        add(rh, b, a, c); //建反边
    }

    cin >> S >> T >> K;
    if (S == T) K++; //最短路中至少要包含一条边,如果S和T相等,那么我们不能直接从S得到T,所以起码需要走一条边,
    //所以求第K+1短就可以了

    //先执行一遍dijkstra算法,求出终点到每个点的最短距离,作为估值
    dijkstra();

    // A*寻路求第K短长度
    printf("%d\n", astar());

    return 0;
}


相关