AcWing 851. spfa求最短路

\(AcWing\) \(851\). \(spfa\)求最短路

一、题目描述

给定一个 \(n\) 个点 \(m\) 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出 \(1\) 号点到 \(n\) 号点的最短距离,如果无法从 \(1\) 号点走到 \(n\) 号点,则输出 impossible

数据保证不存在负权回路。

输入格式
第一行包含整数 \(n\)\(m\)

接下来 \(m\) 行每行包含三个整数 \(x,y,z\),表示存在一条从点 \(x\) 到点 \(y\) 的有向边,边长为 \(z\)

输出格式
输出一个整数,表示 \(1\) 号点到 \(n\) 号点的最短距离。

如果路径不存在,则输出 impossible

数据范围
\(1≤n,m≤10^5\),
图中涉及边长绝对值均不超过 \(10000\)

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

二、\(spfa\)算法

\(spfa\)算法是 \(bellman-ford\)算法的 队列优化算法 的别称,通常用于 求含负权边的单源最短路径,以及判负权环

\(bellman-ford\)是一个很傻的算法,因为它一共进行\(n-1\)次,每次把每条边都遍历一次,不管是不是变小了,都判断一次 \(dist[b]=min(dist[b],dist[a]+w)\),其实,\(dist[b]\)如果真的变小,是因为 \(dist[a]\)变小了,它得到利益,换句话说就是前驱变小而受益,所以可以采用宽搜来做优化。

关键问题

  • \(st[]\)作用
    1. 判断当前的点是否已经加入到队列当中了
    2. 已经加入队列的结点不需要反复的加入到队列
    3. 不使用\(st\)数组最终也没有关系,使用可以提升效率

  • \(spfa\)算法看上去和\(Dijstra\)算法长得有一些像但是其中的意义还是 相差甚远:
    • \(Dijkstra\)算法中的\(st[]\)保存的是:当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆

    • \(spfa\)算法中的\(st[]\)表示当前发生过更新的点,且\(spfa\)中的\(st[]\)数组 可逆 (在标记为\(true\)之后又标记为\(false\))。
      顺带一提的是\(bfs\)中的\(st\)数组记录的是当前已经被遍历过的点

    • \(Dijkstra\)算法里使用的是 优先队列, 保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;\(spfa\)算法中使用的是队列,目的只是记录一下当前发生过更新的点。

  • \(bellman-ford\)算法里最后return -1的判断条件写的是dist[n]>0x3f3f3f3f/5;而\(spfa\)算法写的是dist[n]==0x3f3f3f3f;其原因在于\(bellman-ford\)算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是\(spfa\)算法不一样,它相当于采用了\(bfs\),因此遍历到的结点都是与源点连通的,因此如果你要求的\(n\)和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f

  • \(bellman-ford\)算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是\(spfa\)算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用\(spfa\)否则会死循环。

  • 由于\(spfa\)算法是由\(bellman-ford\)算法优化而来,在最坏的情况下时间复杂度和它一样即时间复杂度为 \(O(nm)\) ,假如题目时间允许可以直接用\(spfa\)算法去解\(Dijkstra\)算法的题目。(好像\(spfa\)有点小小万能的感觉?)

  • 求负环一般使用\(spfa\)算法,方法是用一个\(cnt\)数组记录每个点到源点的边数,一个点被更新一次就+\(1\),一旦有点的边数达到了\(n\)那就证明存在了负环。

三、C++ 代码

#include <bits/stdc++.h>
const int N = 100010, M = 2 * N;
const int INF = 0x3f3f3f3f;

using namespace std;
int n, m;  // 总点数
int d[N];  // 存储每个点到1号点的最短距离
int st[N]; // 存储每个点是否在队列中

// 邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
void spfa(int s) {
    // 初始化距离
    memset(d, 0x3f, sizeof d);
    d[s] = 0;
    queue<int> q;
    q.push(s);
    st[s] = 1;

    while (q.size()) {
        int u = q.front();
        q.pop();
        st[u] = 0;
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if (d[v] > d[u] + w[i]) {
                d[v] = d[u] + w[i];
                // 如果队列中已存在v,则不需要将v重复插入,优化一下
                if (!st[v]) {
                    q.push(v);
                    st[v] = 1;
                }
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    spfa(1);
    if (d[n] == INF)
        puts("impossible");
    else
        printf("%d\n", d[n]);
    return 0;
}
posted @ 2021-09-22 15:58  糖豆爸爸  阅读(396)  评论(0编辑  收藏  举报
Live2D