AcWing 1127 香甜的黄油


题目传送门

算法分析
枚举所有点作为特定牧场,求特定牧场到所有点的最短距离
点的个数\(n= 800\),边的个数\(m=1500\)

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

堆优化版\(dijkstra\) 复杂度是\(O(n\times m \times log_n)\) \(n \times m \times log_n=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\)吧:

一、SPFA

#include 

using namespace std;
typedef pair 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 dist[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 start) {
    //多轮使用spfa,所以一定要清空
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;

    //普通队列
    queue q;
    q.push(start);
    st[start] = true; // st数组标记当前在队列中的点

    while (q.size()) {
        int u = q.front();
        q.pop();
        st[u] = false;

        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[u] + w[i] < dist[j]) {
                dist[j] = dist[u] + w[i];
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    int res = 0;
    for (int i = 1; i <= n; i++) {
        if (dist[id[i]] == INF) return INF;
        res += dist[id[i]];
    }
    return res;
}
int main() {
    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));
    cout << ans << endl;
    return 0;
}

二、Dijkstra+PII

#include 

using namespace std;
typedef pair 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 dist[N]; //记录起点到任意点的最短路径
int q[N];    // spfa用到的队列
bool st[N];  //标识每个牧场是否入过队列
/*
Dijkstra算法是由荷兰神犇Dijkstra发明的最短路算法,是目前最快的寻路算法,
堆优化后时间复杂度可达O((m+n)logm),但缺点是不能处理负权边。
 */
int dijkstra(int start) {
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    priority_queue, greater> q;
    q.push({0, start});

    while (q.size()) {
        PII t = q.top();
        q.pop();

        int u = t.second, d = t.first;
        if (!st[u]) {
            st[u] = true;
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (dist[j] > d + w[i]) {
                    dist[j] = d + w[i];
                    q.push({dist[j], j});
                }
            }
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i++) {
        int j = id[i];
        if (dist[j] == INF) return INF;
        res += dist[j];
    }
    return res;
}
int main() {
    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));
    cout << ans << endl;
    return 0;
}

三、Dijkstra+Struct

#include 

using namespace std;
const int N = 810;  //牧场数 上限800
const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
const int INF = 0x3f3f3f3f;
struct Node {
    int dist;
    int id;
};
bool operator<(const Node &a, const Node &b) {
    return a.dist > b.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 dist[N]; //记录起点到任意点的最短路径
int q[N];    // spfa用到的队列
bool st[N];  //标识每个牧场是否入过队列

int dijkstra(int start) {
    //因为调用多次,需要每次初始化
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    priority_queue q;
    q.push({0, start});

    while (q.size()) {
        auto t = q.top();
        q.pop();

        int u = t.id, d = t.dist;
        if (!st[u]) {
            st[u] = true;
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (dist[j] > d + w[i]) {
                    dist[j] = d + w[i];
                    q.push({dist[j], j});
                }
            }
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i++) {
        int j = id[i];
        if (dist[j] == INF) return INF;
        res += dist[j];
    }
    return res;
}
int main() {
    //用来装图的链表头数组,只需初始化一次
    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));
    cout << ans << endl;
    return 0;
}