• AcWing 1127 香甜的黄油


    \(AcWing\) \(1127\) 香甜的黄油

    题目传送门

    一、题目描述

    农夫\(John\)发现了做出全威斯康辛州最甜的黄油的方法:

    把糖放在一片牧场上,他知道 \(N\) 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。

    当然,他将付出额外的费用在奶牛上。

    农夫\(John\)很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。

    他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

    农夫\(John\)知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。

    给出各头牛在的牧场和牧场间的路线,找出使 所有牛到达的路程和最短的牧场(他将把糖放在那)。

    数据保证至少存在一个牧场和所有牛所在的牧场连通。

    输入格式

    第一行: 三个数:奶牛数 \(N\),牧场数 \(P\),牧场间道路数 \(C\)

    第二行到第 \(N+1\) 行: \(1\)\(N\) 头奶牛所在的牧场号。

    \(N+2\) 行到第 \(N+C+1\) 行:每行有三个数:相连的牧场\(A、B\),两牧场间距 \(D\),当然,连接是双向的。

    输出格式
    共一行,输出奶牛必须行走的最小的距离和。

    二、算法分析

    枚举所有点作为特定牧场,求特定牧场到所有点的最短距离

    点的个数\(n= 800\),边的个数\(m=1500\)

    朴素版\(Dijkstra\) 复杂度是\(O(n^3)\) \(n^3=5.12∗10^8\) 垃圾中的战斗机,不用背代码了~

    堆优化版\(dijkstra\) 复杂度是\(O(n\times m \times log_2n)\) = \(n \times m \times log_2n \approx 800 \times 1500 \times 10 = 1.2∗10^7\)

    \(spfa\) 复杂度是\(O(nm)\) 平均是\(2\)\(3\)倍即 \(3 \times n \times m\)=\(3.9∗10^6\)

    \(spfa\)好强,时间复杂度 \(O(nm)\),最坏\(O(n^2m)\) 但这玩意太玄学,如果出题人故意卡你一下,就可能退化到\(O(n^2\times m)\)垃圾的要死,太不稳定。

    一般用于求带负权边的最短路,全是正权边的还是用堆优化的\(Dijkstra\)吧:

    三、\(Dijkstra+PII\)

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef pair<int, int> PII;
    const int N = 810;  //牧场数 上限800
    const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
    const int INF = 0x3f3f3f3f;
    //链式前向星
    int h[N], e[M], w[M], ne[M], idx;
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
    int id[N];   //每只奶牛在哪个牧场
    int d[N];    //记录起点到任意点的最短路径
    int q[N];    //队列
    bool st[N];  //标识每个牧场是否入过队列
    
    int dijkstra(int S) {
        memset(st, 0, sizeof st);
        memset(d, 0x3f, sizeof d);
        d[S] = 0;
        priority_queue<PII, vector<PII>, greater<PII>> q;
        q.push({0, S});
    
        while (q.size()) {
            PII t = q.top();
            q.pop();
    
            int u = t.second, dist = t.first;
            if (!st[u]) {
                st[u] = true;
                for (int i = h[u]; ~i; i = ne[i]) {
                    int j = e[i];
                    if (d[j] > dist + w[i]) {
                        d[j] = dist + w[i];
                        q.push({d[j], j});
                    }
                }
            }
        }
        int res = 0;
        for (int i = 1; i <= n; i++) {   //遍历每只奶牛
            int j = id[i];               // j号牧场
            if (d[j] == INF) return INF; //如果j号牧场失联了,则无法获得结果
            res += d[j];                 //累加一个最小距离
        }
        return res; //整体的最小距离
    }
    int main() {
        //加快读入
        cin.tie(0), ios::sync_with_stdio(false);
        memset(h, -1, sizeof h);
        cin >> n >> p >> m;                        //奶牛数,牧场数,牧场间道路数
        for (int i = 1; i <= n; i++) cin >> id[i]; // 1 到 N 头奶牛所在的牧场号
    
        while (m--) {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c), add(b, a, c);
        }
        int ans = INF;
    
        //枚举每个牧场为出发点,计算它的最短距离和 中的最小值
        for (int i = 1; i <= p; i++) ans = min(ans, dijkstra(i));
    
        printf("%d\n", ans);
        return 0;
    }
    

    四、\(Dijkstra+Struct\)

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 810;  //牧场数 上限800
    const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
    const int INF = 0x3f3f3f3f;
    struct Node {
        int id;
        int dist;
        bool operator<(const Node &t) const {
            return dist > t.dist; //小顶堆
        }
    };
    
    //链式前向星
    int h[N], e[M], w[M], ne[M], idx;
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
    int id[N];   //每只奶牛在哪个牧场
    int d[N];    //记录起点到任意点的最短路径
    int q[N];    // spfa用到的队列
    bool st[N];  //标识每个牧场是否入过队列
    
    int dijkstra(int start) {
        //因为调用多次,需要每次初始化
        memset(st, 0, sizeof st);
        memset(d, 0x3f, sizeof d);
        d[start] = 0;
        priority_queue<Node> q;
        q.push({start, 0});
    
        while (q.size()) {
            auto t = q.top();
            q.pop();
    
            int u = t.id, dist = t.dist;
            if (!st[u]) {
                st[u] = true;
                for (int i = h[u]; ~i; i = ne[i]) {
                    int j = e[i];
                    if (d[j] > dist + w[i]) {
                        d[j] = dist + w[i];
                        q.push({j, d[j]});
                    }
                }
            }
        }
        int res = 0;
        for (int i = 1; i <= n; i++) {
            int j = id[i];
            if (d[j] == INF) return INF;
            res += d[j];
        }
        return res;
    }
    int main() {
        //加快读入
        cin.tie(0), ios::sync_with_stdio(false);
        //用来装图的链表头数组,只需初始化一次
        memset(h, -1, sizeof h);
        cin >> n >> p >> m;
        for (int i = 1; i <= n; i++) cin >> id[i];
        while (m--) {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c), add(b, a, c);
        }
        int ans = INF;
        for (int i = 1; i <= p; i++) ans = min(ans, dijkstra(i));
        
        printf("%d\n",ans);
        return 0;
    }
    

    五、\(SPFA\)

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef pair<int, int> PII;
    const int N = 810;  //牧场数 上限800
    const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
    const int INF = 0x3f3f3f3f;
    //链式前向星
    int h[N], e[M], w[M], ne[M], idx;
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
    int id[N];   //每只奶牛在哪个牧场
    int d[N];    //记录起点到任意点的最短路径
    int q[N];    // spfa用到的队列
    bool st[N];  //标识每个牧场是否入过队列
    /*
    该算法常用于求含负权图的单源最短路,是Bellman_ford算法的优化版。
    如果a点到终点途中经过b点,那么只有dist[a]更新变小后,dist[b]更新之后才有可能变小。
    所以spfa算法相较于Bellman-ford算法优化的点就是:只有x的邻点的最短路被更新变小之后,x的最短路才需要进行更新判断,操作通过队列实现。
    一般时间复杂度:O(m)。
    最坏时间复杂度:O(n*m)。
    其中n:点的个数,m:边的个数,如果出题人卡常数,那么SPFA会被卡掉。
    */
    int spfa(int S) {
        //多轮使用spfa,所以一定要清空
        memset(st, 0, sizeof st);
        memset(d, 0x3f, sizeof d);
        d[S] = 0;
    
        //普通队列
        queue<int> q;
        q.push(S);
        st[S] = true; // st数组标记当前在队列中的点
    
        while (q.size()) {
            int u = q.front();
            q.pop();
            st[u] = false; //出队列,u不在队列中了
    
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (d[u] + w[i] < d[j]) {
                    d[j] = d[u] + w[i];
                    if (!st[j]) {
                        q.push(j);
                        st[j] = true;
                    }
                }
            }
        }
    
        int res = 0;
        for (int i = 1; i <= n; i++) {
            if (d[id[i]] == INF) return INF;
            res += d[id[i]];
        }
        return res;
    }
    int main() {
        //加快读入
        cin.tie(0), ios::sync_with_stdio(false);
        memset(h, -1, sizeof h);
        cin >> n >> p >> m;
        for (int i = 1; i <= n; i++) cin >> id[i];
    
        while (m--) {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c), add(b, a, c);
        }
        int ans = INF;
        for (int i = 1; i <= p; i++) ans = min(ans, spfa(i));
        printf("%d\n", ans);
        return 0;
    }
    

    六、\(Floyd\)解法

    通过了 8/12个数据,不能\(AC\)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    
    using namespace std;
    const int N = 810;
    const int M = 1460;
    const int INF = 0x3f3f3f3f;
    
    // 通过了 8/12个数据
    int id[N], cnt[N], g[N][N];
    int n, p, m, ans = INF;
    
    void calc(int S) {
        int q = 0;
        for (int i = 1; i <= p; i++) q += g[S][i] * cnt[i];
        ans = min(ans, q);
    }
    
    int main() {
        //加快读入
        cin.tie(0), ios::sync_with_stdio(false);
        cin >> n >> p >> m;
        for (int i = 1; i <= n; i++) { // n只奶牛
            cin >> id[i];
            cnt[id[i]]++;
        }
    
        // Floyd初始化
        memset(g, 0x3f, sizeof g);
        for (int i = 1; i <= p; i++) g[i][i] = 0;
    
        while (m--) {
            int a, b, c;
            cin >> a >> b >> c;
            g[a][b] = g[b][a] = c;
        }
        // 5行的Floyd算法
        for (int k = 1; k <= p; k++)
            for (int i = 1; i <= p; i++)
                for (int j = 1; j <= p; j++)
                    g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
    
        //枚举每个点为出发点
        for (int i = 1; i <= p; i++) calc(i);
        printf("%d\n", ans);
        return 0;
    }
    
  • 相关阅读:
    417 Pacific Atlantic Water Flow 太平洋大西洋水流
    416 Partition Equal Subset Sum 分割相同子集和
    415 Add Strings 字符串相加
    414 Third Maximum Number 第三大的数
    413 Arithmetic Slices 等差数列划分
    412 Fizz Buzz
    410 Split Array Largest Sum 分割数组的最大值
    409 Longest Palindrome 最长回文串
    day22 collection 模块 (顺便对比queue也学习了一下队列)
    day21 计算器作业
  • 原文地址:https://www.cnblogs.com/littlehb/p/16003003.html
Copyright © 2020-2023  润新知