• [模板]单源最短路径


    笔记性质,记录一下单源最短路径的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
    ", 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判断)

  • 相关阅读:
    Android View 阴影的总结
    清晰的教你如何将 Maven 项目上传至 中央仓库以及版本更新
    简单粗暴的上传项目至 Github
    App自动更新(DownloadManager下载器)
    类型判断
    前端防御XSS
    window.location.href/replace/reload()/页面跳转+替换+刷新
    对数组排序进行"洗牌"(随机排序)
    iframe跨域上传图片
    Vim 新手节省时间的小技巧
  • 原文地址:https://www.cnblogs.com/Gaomez/p/14053892.html
Copyright © 2020-2023  润新知