[模板]单源最短路径

笔记性质,记录一下单源最短路径的SPFA,Dijkstra,Bellman-Ford算法.

先来做个对比:

 SPFA实质是Bellman-Ford的队列优化版本,但这个优化并不改变最坏情况的复杂度,所以他死了.

Dijkstra的复杂度很好,但是根本不能碰负权.

那么负权最短路算法有复杂度更低的算法吗?暂时没有接触到.

 


1.Bellman-Ford&SPFA

Bellman-Ford算法不停枚举并尝试松弛每一条边以达到求最短路的目的,SPFA是其优化版本.

P3371 单源最短路径(弱化版)

这是一道用来测试SPFA和Bellman-Ford的题目,不过没有负权(当然也就没有负环).

如果可以写SPFA,似乎没有必要写Bellman-Ford,写后者的理由不过是容易写而已(不论是求最短路还是判负环).

Bellman-Ford若要判断是否有负环,只需要在进行n-1次对每条边松弛操作后检查是否仍有可松弛的边,有则存在负环.

注意此时的边有独特的存储方式.

(当时码风还没形成,建议看这篇文章里的实现)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

#define MAX_E 500010
#define MAX_V 10010

struct edge {
    int from, to, cost;
};

edge es[MAX_E];
int d[MAX_V], V, E, s;
const int INF = 2147483648 - 1;

void solve() {
    while (1) {
        bool bad = true;
        for (int i = 1; i <= E; i++) {
            edge e = es[i];
            if (d[e.from] != INF && d[e.to] > d[e.from] + e.cost) {
                d[e.to] = d[e.from] + e.cost;
                bad = false;
            }
        }
        if (bad) break;
    }
}

int main() {
    // freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    cin >> V >> E >> s;
    for (int i = 1; i <= E; i++) cin >> es[i].from >> es[i].to >> es[i].cost;

    for (int i = 0; i <= V; i++) d[i] = INF;
    d[s] = 0;

    solve();

    for (int i = 1; i <= V; i++) cout << d[i] << ' ';
    cout << endl;

    return 0;
}
P3371 Bellman-Ford

 

P3358 负环

现在用SPFA求最短路,顺便判一下负环.

基于这样的一个事实:每当一条边被从队列中取出,则说明其在入队时进行了一次松弛操作.

因此可以记录每一条边的出队次数即松弛次数,一旦发现某条边被松弛了第n次,则存在负环.

相比Bellman-Ford,多了个队列,多了个used数组,又多了个计数数组,不过只需要使用普通的vector邻接表.

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct E {
    int to, wei;
};
vector<E> e[2010];
int n, m, dist[2010], ct[2010];
bool used[2010];

bool solve() {
    memset(used, 0, sizeof(used));
    memset(ct, 0, sizeof(ct));
    memset(dist, 0x3F, sizeof(dist));
    dist[1] = 0;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) e[i].clear();
    while (m--) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        e[u].push_back({v, w});
        if (w >= 0) e[v].push_back({u, w});
    }

    queue<int> q;
    q.push(1);
    used[1] = true;
    while(!q.empty()){
        int cur = q.front();
        q.pop();
        used[cur] = false,
        ++ct[cur];
        if(ct[cur] >= n) return 1;

        for(auto i : e[cur])
            if(dist[i.to] > dist[cur] + i.wei){
                dist[i.to] = dist[cur] + i.wei;
                if(!used[i.to]){
                    used[i.to] = true;
                    q.push(i.to);
                }
            }
    }

    return 0;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) puts(solve() ? "YES" : "NO");

    return 0;
}
P3385 SPFA

 

昂贵的聘礼

相比于Dijkstra,SPFA有时候具有更强的灵活性.

本题中对于节点入队有了限制条件:新入队节点与先前遍历的点的等级必须保持在一定范围内.

由于SPFA与BFS的相似性质,可以很方便地将已有等级的信息设计到队列元素中来处理新节点.

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

struct E{
    int to, wei;
};
struct Q{
    int to, l, r;
};
vector<E> e[110];
queue<Q> q;
int n, m, lv[110], dist[110];
bool used[110];

int main(){
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        e[0].push_back({i, a});
        lv[i] = b;
        while(c--){
            int u, v;
            scanf("%d%d", &u, &v);
            e[u].push_back({i, v});
        }
    }
    lv[0] = lv[1];

    memset(dist, 0x3F, sizeof(dist));
    q.push({0, lv[0], lv[0]});
    dist[0] = 0;
    while(!q.empty()){
        Q cur = q.front();
        q.pop();
        used[cur.to] = false;

        for(vector<E>::iterator i = e[cur.to].begin(); i != e[cur.to].end(); i++)
            if(dist[cur.to] + i->wei < dist[i->to] && cur.r - lv[i->to] <= m && lv[i->to] - cur.l <= m){
                dist[i->to] = dist[cur.to] + i->wei;
                if(!used[i->to]){
                    used[i->to] = true;
                    q.push({i->to, min(cur.l, lv[i->to]), max(cur.r, lv[i->to])});
                }
            }
    }

    printf("%d\n", dist[1]);

    return 0;
}
POJ 昂贵的聘礼

 

2.Dijkstra

Dijkstra算法通过维护集合并寻找集合外最近节点以计算最短路.

由于其贪心思想,不能处理负权图.但是复杂度很好.

P4779 单源最短路径(标准版)

如果可以的话就用Dijkstra吧,因为

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct E {
    int to, wei;
    bool operator<(const E &other) const { return wei > other.wei; }
};
vector<E> e[100010];
int n, m, s, dist[100010];
bool used[100010];

int main() {
    scanf("%d%d%d", &n, &m, &s);
    while(m--){
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        e[u].push_back({v, w});
    }

    priority_queue<E> q;
    q.push({s, 0});
    while(!q.empty()){
        E cur = q.top();
        q.pop();
        if(used[cur.to]) continue;

        used[cur.to] = true;
        dist[cur.to] = cur.wei;
        for(auto i : e[cur.to])
            q.push({i.to, i.wei + cur.wei});
    }

    for(int i = 1; i <= n; i++) printf("%d ", dist[i]);
    puts("");

    return 0;
}
P4779 Dijkstra

 (可以删掉used,把dist初始化为-1作为used判断)

posted @ 2021-02-26 22:07  goverclock  阅读(83)  评论(0编辑  收藏  举报