【图论】Dijkstra算法

适用于非负权图,所有的边权都是非负数。
或者从s节点出发的子图中,边权都是非负数的也可以。(从s出发没办法适用的,是负数甚至复数都无所谓。)

Dijkstra算法

标准版本,带有vis数组方便检查是否从s出发可以到达i。

下面的版本中是从1开始的n个点。如果是从0开始的话要改一下。

namespace Dijkstra {

    const int MAXN = 2e5 + 10;

    int n;
    vector<pii> G[MAXN];

    ll dis[MAXN];
    bool vis[MAXN];

    priority_queue<pli> PQ;

    void dijkstra(int s) {
        fill(dis + 1, dis + 1 + n, LINF);
        fill(vis + 1, vis + 1 + n, 0);
        while(!PQ.empty())
            PQ.pop();
        dis[s] = 0;
        PQ.push({-dis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(pii &p : G[u]) {
                int v = p.first;
                int w = p.second;
                if(vis[v] || dis[v] <= dis[u] + w)
                    continue;
                dis[v] = dis[u] + w;
                PQ.push({-dis[v], v});
            }
        }
    }

}

可能平均情况快一点的版本,拥有一个终点t,可以在非最坏情况下提前使得算法终止。由于算法提前终止所以除t以外的点不一定代表最终结果。

    ll dijkstra(int s, int t) {
        fill(dis + 1, dis + 1 + n, LINF);
        fill(vis + 1, vis + 1 + n, 0);
        while(!PQ.empty())
            PQ.pop();
        dis[s] = 0;
        PQ.push({-dis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            if(u == t)
                break;
            for(pii &p : G[u]) {
                int v = p.first;
                int w = p.second;
                if(vis[v] || dis[v] <= dis[u] + w)
                    continue;
                dis[v] = dis[u] + w;
                PQ.push({-dis[v], v});
            }
        }
        return dis[t];
    }

计算从s出发到点i的最短路的条数

    void dijkstra(int s) {
        fill(dis + 1, dis + 1 + n, LINF);
        fill(cnt + 1, cnt + 1 + n, 0);
        fill(vis + 1, vis + 1 + n, 0);
        while(!PQ.empty())
            PQ.pop();
        dis[s] = 0;
        cnt[s] = 1;
        PQ.push({-dis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(pii &p : G[u]) {
                int v = p.first;
                int w = p.second;
                if(vis[v] || dis[v] < dis[u] + w)
                    continue;
                if(dis[v] == dis[u] + w) {
                    cnt[v] += cnt[u];
                    continue;
                }
                dis[v] = dis[u] + w;
                cnt[v] = cnt[u];
                PQ.push({-dis[v], v});
            }
        }
    }

把默认的优先队列修改为“待修改操作的堆”或者“线段树”可以把空间复杂度从 \(O(n+m)\) 降低到 \(O(n)\) ,但是看起来很花里胡哨。

两点S、T之间的最短路的可经点:

对S和T分别求一次单源最短路。如果disS[i] + disT[i] == disS[T] 则i是可经点。

同理,如果某条边uvw满足 disS[u] + disT[v] + w = disS[T] 或者 disS[v] + disT[u] + w = disS[T] 则这条边是可经边。

上面是对于无向图的做法,对于有向图要区分从T开始求单源最短路时用到的边是和从S开始的时候不一样的。

到达某点的不同的最短路的方案数

如果某边松弛成功,则 dis[v] = dis[u] + w , cnt[v] = cnt[u],如果某边是相等的,无法松弛,那就是cnt[v]+=cnt[u]。

posted @ 2020-11-28 12:38  purinliang  阅读(129)  评论(0编辑  收藏  举报