数据结构与算法-图论-复习1(单源最短路,全源最短路,最小生成树)

1. 单源最短路

单一边权 BFS

原理:由于边权为单一值,可使用广度优先搜索(BFS)来求解最短路。BFS 会逐层扩展节点,由于边权相同,第一次到达某个节点时的路径长度就是最短路径长度。

用法:适用于边权都为 1 的图,可快速求出从源点到其他所有节点的最短路径。

代码:

#include <iostream>#include <queue>#include <vector>using namespace std;

const int MAXN = 100005;

vector<int> adj[MAXN];int dist[MAXN];

void bfs(int s) {

    queue<int> q;

    fill(dist, dist + MAXN, -1);

    dist[s] = 0;

    q.push(s);

    while (!q.empty()) {

        int u = q.front();

        q.pop();

        for (int v : adj[u]) {

            if (dist[v] == -1) {

                dist[v] = dist[u] + 1;

                q.push(v);

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v;

        cin >> u >> v;

        adj[u].push_back(v);

        adj[v].push_back(u);

    }

    bfs(s);

    for (int i = 1; i <= n; i++) {

        cout << dist[i] << " ";

    }

    cout << endl;

    return 0;

}

0 - 1 边权双端队列 BFS

原理:使用双端队列来优化 BFS 过程。对于边权为 0 的边,将其终点插入队头;对于边权为 1 的边,将其终点插入队尾。这样能保证队列中元素的距离是单调递增的。

用法:适用于图中边权只有 0 和 1 的情况。

代码:

#include <iostream>#include <deque>#include <vector>using namespace std;

const int MAXN = 100005;

vector<pair<int, int>> adj[MAXN];int dist[MAXN];

void bfs_01(int s) {

    deque<int> q;

    fill(dist, dist + MAXN, -1);

    dist[s] = 0;

    q.push_back(s);

    while (!q.empty()) {

        int u = q.front();

        q.pop_front();

        for (auto [v, w] : adj[u]) {

            if (dist[v] == -1) {

                dist[v] = dist[u] + w;

                if (w == 0) {

                    q.push_front(v);

                } else {

                    q.push_back(v);

                }

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        adj[u].emplace_back(v, w);

        adj[v].emplace_back(u, w);

    }

    bfs_01(s);

    for (int i = 1; i <= n; i++) {

        cout << dist[i] << " ";

    }

    cout << endl;

    return 0;

}

朴素迪杰斯特拉

原理:通过贪心策略,每次从未确定最短路径的节点中选择距离源点最近的节点,然后以该节点为中间点更新其他节点的距离。

用法:适用于边权非负且节点数较少的图,时间复杂度为 O(V2)稠密图。

代码:

#include <iostream>#include <vector>#include <climits>using namespace std;

const int MAXN = 1005;int graph[MAXN][MAXN];int dist[MAXN];bool vis[MAXN];

void dijkstra(int s, int n) {

    fill(dist, dist + MAXN, INT_MAX);

    fill(vis, vis + MAXN, false);

    dist[s] = 0;

    for (int i = 0; i < n; i++) {

        int u = -1;

        for (int j = 1; j <= n; j++) {

            if (!vis[j] && (u == -1 || dist[j] < dist[u])) {

                u = j;

            }

        }

        vis[u] = true;

        for (int v = 1; v <= n; v++) {

            if (!vis[v] && graph[u][v] != INT_MAX) {

                dist[v] = min(dist[v], dist[u] + graph[u][v]);

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            if (i == j) {

                graph[i][j] = 0;

            } else {

                graph[i][j] = INT_MAX;

            }

        }

    }

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        graph[u][v] = min(graph[u][v], w);

    }

    dijkstra(s, n);

    for (int i = 1; i <= n; i++) {

        cout << dist[i] << " ";

    }

    cout << endl;

    return 0;}

堆优化迪杰斯特拉

原理:使用优先队列(小根堆)来优化每次选择距离源点最近的节点的过程,减少时间复杂度。

用法:适用于边权非负且节点数较多的稀疏图,时间复杂度为 O((V+E)logV)。

代码:

#include <iostream>#include <vector>#include <queue>#include <climits>using namespace std;

const int MAXN = 100005;

vector<pair<int, int>> adj[MAXN];int dist[MAXN];

void dijkstra(int s) {

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

    fill(dist, dist + MAXN, INT_MAX);

    dist[s] = 0;

    pq.push({0, s});

    while (!pq.empty()) {

        auto [d, u] = pq.top();

        pq.pop();

        if (d > dist[u]) continue;

        for (auto [v, w] : adj[u]) {

            if (dist[v] > dist[u] + w) {

                dist[v] = dist[u] + w;

                pq.push({dist[v], v});

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        adj[u].emplace_back(v, w);

    }

    dijkstra(s);

    for (int i = 1; i <= n; i++) {

        cout << dist[i] << " ";

    }

    cout << endl;

    return 0;}

Bellman - Ford

原理:通过对所有边进行 V−1 次松弛操作,来更新节点的最短距离。如果图中存在负环,经过 V−1 次松弛操作后,仍然可以继续松弛。

用法:适用于存在负权边的图,也可用于求解只经过 k 条边的最短路。

代码:

#include <iostream>#include <vector>#include <climits>using namespace std;

const int MAXN = 100005;struct Edge {

    int u, v, w;};

vector<Edge> edges;int dist[MAXN];

bool bellman_ford(int s, int n) {

    fill(dist, dist + MAXN, INT_MAX);

    dist[s] = 0;

     int flag=0;

    for (int i = 0; i < n ; i++) {

        for (auto [u, v, w] : edges) {

            flag=0;

            if (dist[u] != INT_MAX && dist[v] > dist[u] + w) {

                dist[v] = dist[u] + w;

                 flag=1;

                 if(i==n)return true;//如果链接了n条边还能松弛就说明有负环

            }

        }

    }

    return false;

}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        edges.push_back({u, v, w});

    }

    if (bellman_ford(s, n)) {

        cout << "存在负环" << endl;

    } else {

        for (int i = 1; i <= n; i++) {

            cout << dist[i] << " ";

        }

        cout << endl;

    }

    return 0;

}

SPFA

原理:SPFA 是 Bellman - Ford 算法的优化版本,使用队列来维护需要松弛的节点。

用法:适用于存在负权边的图,但在某些特殊图中可能会退化为 O(VE)。

代码:

#include <iostream>#include <vector>#include <queue>#include <climits>using namespace std;

const int MAXN = 100005;

vector<pair<int, int>> adj[MAXN];int dist[MAXN];int cnt[MAXN]; // 记录每个节点入队的次数bool in_queue[MAXN];

bool spfa(int s, int n) {

    fill(dist, dist + MAXN, INT_MAX);

    fill(cnt, cnt + MAXN, 0);

    fill(in_queue, in_queue + MAXN, false);

    dist[s] = 0;

    queue<int> q;

    q.push(s);

    in_queue[s] = true;

    cnt[s] = 1;

    while (!q.empty()) {

        int u = q.front();

        q.pop();

        in_queue[u] = false;

        for (auto [v, w] : adj[u]) {

            if (dist[v] > dist[u] + w) {

                dist[v] = dist[u] + w;

                if (!in_queue[v]) {

                    q.push(v);

                    in_queue[v] = true;

                    cnt[v]++;

                    if (cnt[v] > n) {

                        return true; // 存在负环

                    }

                }

            }

        }

    }

    return false;}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        adj[u].emplace_back(v, w);

    }

    if (spfa(s, n)) {

        cout << "存在负环" << endl;

    } else {

        for (int i = 1; i <= n; i++) {

            cout << dist[i] << " ";

        }

        cout << endl;

    }

    return 0;}

2. 单源最短路和其他算法的结合

二分答案 + 双端队列求最短路

以 “可以免费建立 k 条边,求最小的花费” 为例,二分答案 mid,将边权大于 mid 的边视为 1,边权小于等于 mid 的边视为 0,使用双端队列 BFS 求解最短路,判断最短路径上大于 mid 的边数是否小于等于 k。

缩点 + 最短路

先使用 Tarjan 算法求出图的强连通分量,然后进行缩点,将每个强连通分量缩成一个点,再在缩点后的图上进行最短路计算。

首位两次最短路求节点之间的最大差值(以 2009 年提高组最优贸易问题为例)

分别从起点和终点进行最短路计算,记录从起点到每个节点的最小价格和从终点到每个节点的最大价格,然后枚举每个节点,计算最大差值。

3. 单源最短路自身的拓展

多起点多终点用虚拟节点

添加一个虚拟起点和一个虚拟终点,将虚拟起点与所有起点相连,边权为 0,将所有终点与虚拟终点相连,边权为 0,然后求解从虚拟起点到虚拟终点的最短路。

分图层的最短路

以 “拯救大兵瑞恩” 为例,使用三维数组 d[x][y][st] 记录状态,其中 (x,y) 表示节点坐标,st 表示当前拥有的钥匙状态,只有拿到了钥匙才能进入到对应层的一些状态,在 BFS 或 Dijkstra 过程中更新状态。

新的一个例题:

题目背景
本题原题数据极弱,Subtask 0 中的测试点为原题测试点,Subtask 1 中的测试点为 Hack 数据。

题目描述
C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

假设 1∼n 号城市的水晶球价格分别为 4,3,5,6,1。

阿龙可以选择如下一条线路:1→2→3→5,并在 2 号城市以 3 的价格买入水晶球,在 3 号城市以 5 的价格卖出水晶球,赚取的旅费数为 2。

阿龙也可以选择如下一条线路:1→4→5→4→5,并在第 1 次到达 5 号城市时以 1 的价格买入水晶球,在第 2 次到达 4 号城市时以 6 的价格卖出水晶球,赚取的旅费数为 5。

现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

输入格式
第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。

第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。

接下来 m 行,每行有 3 个正整数 x,y,z,每两个整数之间用一个空格隔开。如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。

输出格式
一个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0。

代码:

分层图,第一层原本的图,第二层,第一层购买后到达的图,第三层,第二层对应节点卖出的图

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=100010;
unordered_map<int,vector<pii>>e;
int n,m;
int a[N];
int dis[N*3];
bool vis[N*3];
void spfa(int s){
    memset(dis,-0x3f,sizeof dis);
    queue<int> q;
    q.push(s);
    dis[s]=0;
    vis[s]=1;
    while(q.size()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(auto t:e[u]){
            int v=t.first,w=t.second;
            if(dis[v]<dis[u]+w){
                dis[v]=dis[u]+w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        e[i].push_back({i+n,-a[i]});
        e[n+i].push_back({i+2*n,a[i]});
    }
    for(int i=0;i<m;i++){
        int u,v,op;
        scanf("%d%d%d",&u,&v,&op);
        if(op==2){
            e[u].push_back({v,0});
            e[u+n].push_back({v+n,0});
            e[u+2*n].push_back({v+2*n,0});
            e[v].push_back({u,0});
            e[v+n].push_back({u+n,0});
            e[v+2*n].push_back({u+2*n,0});
        }else{
            e[u].push_back({v,0});
            e[u+n].push_back({v+n,0});
            e[u+2*n].push_back({v+2*n,0});
        }
    }
    spfa(1);
    cout<<dis[3*n];
}
 

最短路计数

在更新最短路径时,记录路径数量。如果经过某个点的路径更短,计数为 1;如果相等,计数累加。

代码:

#include <iostream>#include <vector>#include <queue>#include <climits>using namespace std;

const int MAXN = 100005;const int MOD = 1e9 + 7;

vector<pair<int, int>> adj[MAXN];int dist[MAXN];int cnt[MAXN];

void dijkstra(int s) {

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

    fill(dist, dist + MAXN, INT_MAX);

    fill(cnt, cnt + MAXN, 0);

    dist[s] = 0;

    cnt[s] = 1;

    pq.push({0, s});

    while (!pq.empty()) {

        auto [d, u] = pq.top();

        pq.pop();

        if (d > dist[u]) continue;

        for (auto [v, w] : adj[u]) {

            if (dist[v] > dist[u] + w) {

                dist[v] = dist[u] + w;

                cnt[v] = cnt[u];

                pq.push({dist[v], v});

            } else if (dist[v] == dist[u] + w) {

                cnt[v] = (cnt[v] + cnt[u]) % MOD;

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        adj[u].emplace_back(v, w);

    }

    dijkstra(s);

    for (int i = 1; i <= n; i++) {

        cout << dist[i] << " " << cnt[i] << endl;

    }

    return 0;}

最短路和次短路

使用两个数组分别记录最短路和次短路,在更新时同时更新这两个数组。

代码:

#include <iostream>#include <vector>#include <queue>#include <climits>using namespace std;

const int MAXN = 100005;

vector<pair<int, int>> adj[MAXN];int dist1[MAXN]; // 最短路int dist2[MAXN]; // 次短路

void dijkstra(int s) {

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

    fill(dist1, dist1 + MAXN, INT_MAX);

    fill(dist2, dist2 + MAXN, INT_MAX);

    dist1[s] = 0;

    pq.push({0, s});

    while (!pq.empty()) {

        auto [d, u] = pq.top();

        pq.pop();

        if (d > dist2[u]) continue;

        for (auto [v, w] : adj[u]) {

            int new_dist = d + w;

            if (new_dist < dist1[v]) {

                dist2[v] = dist1[v];

                dist1[v] = new_dist;

                pq.push({dist1[v], v});

            } else if (new_dist < dist2[v] && new_dist > dist1[v]) {

                dist2[v] = new_dist;

                pq.push({dist2[v], v});

            }

        }

    }}

int main() {

    int n, m, s;

    cin >> n >> m >> s;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        adj[u].emplace_back(v, w);

    }

    dijkstra(s);

    for (int i = 1; i <= n; i++) {

        cout << dist1[i] << " " << dist2[i] << endl;

    }

    return 0;}

4. 全源最短路

Floyd 算法

原理:通过动态规划的思想,枚举中间节点 k,更新任意两点之间的最短距离。

用法:适用于求解任意两点之间的最短路径,也可用于处理联通问题、传递闭包和最小环问题。

代码:

#include <iostream>#include <climits>using namespace std;

const int MAXN = 1005;int graph[MAXN][MAXN];

void floyd(int n) {

    for (int k = 1; k <= n; k++) {

        for (int i = 1; i <= n; i++) {

            for (int j = 1; j <= n; j++) {

                if (graph[i][k] != INT_MAX && graph[k][j] != INT_MAX) {

                    graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);

                }

            }

        }

    }}

int main() {

    int n, m;

    cin >> n >> m;

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            if (i == j) {

                graph[i][j] = 0;

            } else {

                graph[i][j] = INT_MAX;

            }

        }

    }

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        graph[u][v] = min(graph[u][v], w);

    }

    floyd(n);

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            cout << graph[i][j] << " ";

        }

        cout << endl;

    }

    return 0;}

最小环

在 Floyd 算法的基础上,记录最小环的长度。

cpp

#include <iostream>#include <climits>using namespace std;

const int MAXN = 1005;int graph[MAXN][MAXN];int dist[MAXN][MAXN];

int floyd_min_cycle(int n) {

    int ans = INT_MAX;

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            dist[i][j] = graph[i][j];

        }

    }

    for (int k = 1; k <= n; k++) {

        for (int i = 1; i < k; i++) {

            for (int j = i + 1; j < k; j++) {

                if (graph[i][k] != INT_MAX && graph[k][j] != INT_MAX && dist[i][j] != INT_MAX) {

                    ans = min(ans, graph[i][k] + graph[k][j] + dist[i][j]);

                }

            }

        }

        for (int i = 1; i <= n; i++) {

            for (int j = 1; j <= n; j++) {

                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX) {

                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);

                }

            }

        }

    }

    return ans;

}

int main() {

    int n, m;

    cin >> n >> m;

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            if (i == j) {

                graph[i][j] = 0;

            } else {

                graph[i][j] = INT_MAX;

            }

        }

    }

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        graph[u][v] = min(graph[u][v], w);

        graph[v][u] = min(graph[v][u], w);

    }

    int min_cycle = floyd_min_cycle(n);

    if (min_cycle == INT_MAX) {

        cout << "不存在最小环" << endl;

    } else {

        cout << "最小环长度为: " << min_cycle << endl;

    }

    return 0;

}

5. 最小生成树

Prim 算法

原理:从一个节点开始,每次选择与当前生成树相连的边中权值最小的边,将其加入生成树,直到所有节点都被加入。

用法:适用于稠密图,时间复杂度为 O(V2)。

代码:

#include <iostream>#include <vector>#include <climits>using namespace std;

const int MAXN = 1005;int graph[MAXN][MAXN];bool vis[MAXN];int dist[MAXN];

int prim(int n) {

    fill(vis, vis + MAXN, false);

    fill(dist, dist + MAXN, INT_MAX);

    dist[1] = 0;

    int ans = 0;

    for (int i = 0; i < n; i++) {

        int u = -1;

        for (int j = 1; j <= n; j++) {

            if (!vis[j] && (u == -1 || dist[j] < dist[u])) {

                u = j;

            }

        }

        vis[u] = true;

        ans += dist[u];

        for (int v = 1; v <= n; v++) {

            if (!vis[v] && graph[u][v] != INT_MAX) {

                dist[v] = min(dist[v], graph[u][v]);

            }

        }

    }

    return ans;}

int main() {

    int n, m;

    cin >> n >> m;

    for (int i = 1; i <= n; i++) {

        for (int j = 1; j <= n; j++) {

            if (i == j) {

                graph[i][j] = 0;

            } else {

                graph[i][j] = INT_MAX;

            }

        }

    }

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        graph[u][v] = min(graph[u][v], w);

        graph[v][u] = min(graph[v][u], w);

    }

    int mst = prim(n);

    cout << "最小生成树的权值为: " << mst << endl;

    return 0;}

Kruskal 算法

原理:将所有边按权值从小到大排序,然后依次选择边,如果该边的两个端点不在同一个连通分量中,则将该边加入生成树,直到所有节点都在同一个连通分量中。

用法:适用于稀疏图,时间复杂度为 O(ElogE)。

代码:

#include <iostream>#include <vector>#include <algorithm>using namespace std;

const int MAXN = 100005;struct Edge {

    int u, v, w;

    bool operator<(const Edge& other) const {

        return w < other.w;

    }};

vector<Edge> edges;int parent[MAXN];

int find(int x) {

    if (parent[x] != x) {

        parent[x] = find(parent[x]);

    }

    return parent[x];}

void unite(int x, int y) {

    int px = find(x);

    int py = find(y);

    if (px != py) {

        parent[px] = py;

    }}

int kruskal(int n) {

    sort(edges.begin(), edges.end());

    for (int i = 1; i <= n; i++) {

        parent[i] = i;

    }

    int ans = 0;

    int cnt = 0;

    for (auto [u, v, w] : edges) {

        if (find(u) != find(v)) {

            unite(u, v);

            ans += w;

            cnt++;

            if (cnt == n - 1) {

                break;

            }

        }

    }

    return ans;}

int main() {

    int n, m;

    cin >> n >> m;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        edges.push_back({u, v, w});

    }

    int mst = kruskal(n);

    cout << "最小生成树的权值为: " << mst << endl;

    return 0;}

6. 最小生成树的运用

引入虚拟节点

以 “有 k 个点可以用卫星链接,求出让全部点联通的最小花费” 为例,添加一个虚拟节点,将该节点与可以用卫星链接的 k 个点相连,边权为 0,然后使用 Kruskal 或 Prim 算法求解最小生成树。

允许 k 个联通块的最小花费

使用 Kruskal 算法,在合并节点的过程中,当合并到 k 个联通块时停止,此时的花费即为最小花费。

代码:

#include <iostream>#include <vector>#include <algorithm>using namespace std;

const int MAXN = 100005;struct Edge {

    int u, v, w;

    bool operator<(const Edge& other) const {

        return w < other.w;

    }};

vector<Edge> edges;int parent[MAXN];

int find(int x) {

    if (parent[x] != x) {

        parent[x] = find(parent[x]);

    }

    return parent[x];}

void unite(int x, int y) {

    int px = find(x);

    int py = find(y);

    if (px != py) {

        parent[px] = py;

    }}

int kruskal(int n, int k) {

    sort(edges.begin(), edges.end());

    for (int i = 1; i <= n; i++) {

        parent[i] = i;

    }

    int ans = 0;

    int cnt = n;

    for (auto [u, v, w] : edges) {

        if (find(u) != find(v)) {

            unite(u, v);

            ans += w;

            cnt--;

            if (cnt == k) {

                break;

            }

        }

    }

    return ans;}

int main() {

    int n, m, k;

    cin >> n >> m >> k;

    for (int i = 0; i < m; i++) {

        int u, v, w;

        cin >> u >> v >> w;

        edges.push_back({u, v, w});

    }

    int cost = kruskal(n, k);

    cout << "允许 " << k << " 个联通块的最小花费为: " << cost << endl;

return 0;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76215.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【WRF理论第十七期】单向/双向嵌套机制(含namelist.input详细介绍)

WRF运行的单向/双向嵌套机制 准备工作&#xff1a;WRF运行的基本流程namelist.input的详细设置&time_control 设置&domain 嵌套结构&bdy_control 配置部分 namelist 其他注意事项Registry.EM 运行 ARW 嵌套双向嵌套&#xff08;two-way nesting&#xff09;单向嵌套…

怎么查看苹果手机和ipad的设备信息和ios udid

你知道吗&#xff1f;我们每天使用的iPhone和iPad&#xff0c;其实隐藏着大量详细的硬件与系统信息。除了常见的系统版本和序列号外&#xff0c;甚至连电池序列号、摄像头序列号、销售地区、芯片型号等信息&#xff0c;也都可以轻松查到&#xff01; 如果你是开发者、维修工程…

matlab内置的git软件版本管理功能

1、matlab多人协作开发比普通的嵌入式软件开发困难很多 用过matlab的人都知道&#xff0c;版本管理对于matlab来说真的很费劲&#xff0c;今天介绍的这个工具也不是说它就解决了这个痛点&#xff0c;只是让它变得简单一点。版本管理肯定是不可或缺的&#xff0c;干就完了 2、…

vscode集成deepseek实现辅助编程(银河麒麟系统)【详细自用版】

针对开发者用户&#xff0c;可在Visual Studio Code中接入DeepSeek&#xff0c;实现辅助编程。 可参考我往期文章在银河麒麟系统环境下部署DeepSeek&#xff1a;基于银河麒麟桌面&&服务器操作系统的 DeepSeek本地化部署方法【详细自用版】 一、前期准备 &#xff08…

Java 大厂面试题 -- JVM 深度剖析:解锁大厂 Offe 的核心密钥

最近佳作推荐&#xff1a; Java大厂面试高频考点&#xff5c;分布式系统JVM优化实战全解析&#xff08;附真题&#xff09;&#xff08;New&#xff09; Java大厂面试题 – JVM 优化进阶之路&#xff1a;从原理到实战的深度剖析&#xff08;2&#xff09;&#xff08;New&#…

数据库实践题目:在线书店管理系统

完整的数据库实践题目&#xff1a;在线书店管理系统 数据库表结构及示例数据 书籍表(books) CREATE TABLE books ( book_id INT PRIMARY KEY, title VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, publisher VARCHAR(50), publish_year INT, category VARCHAR(30), …

Linux 入门指令(1)

&#xff08;1&#xff09;ls指令 ls -l可以缩写成 ll 同时一个ls可以加多个后缀 比如 ll -at (2)pwd指令 &#xff08;3&#xff09;cd指令 cd .是当前目录 &#xff08;4&#xff09;touch指令 &#xff08;5&#xff09;mkdir指令 &#xff08;6&#xff09;rmdir和rm…

图灵逆向——题七-千山鸟飞绝

目录列表 过程分析headers头部M参数分析载荷x参数分析响应数据解密分析 代码实现 一进来还是一个无限debugger&#xff0c;前面有讲怎么过&#xff0c;这里直接过掉~ 老规矩&#xff0c;养成习惯&#xff0c;先看请求头里有没有加密参数发现好像是有个M&#xff0c;它是个32位…

上门预约洗鞋店小程序都具备哪些功能?

现在大家对洗鞋子的清洗条件越来越高&#xff0c;在家里不想去&#xff0c;那就要拿去洗鞋店去洗。如果有的客户没时间去洗鞋店&#xff0c;这个时候&#xff0c;有个洗鞋店小程序就可以进行上门取件&#xff0c;帮助没时间的客户去取需要清洗的鞋子&#xff0c;这样岂不是既帮…

Node.js EventEmitter 深入解析

Node.js EventEmitter 深入解析 概述 Node.js 作为一种强大的 JavaScript 运行环境&#xff0c;以其异步、事件驱动特性在服务器端编程中占据了重要地位。EventEmitter 是 Node.js 中处理事件的一种机制&#xff0c;它允许对象&#xff08;称为“发射器”&#xff09;发出事件…

C++11QT复习 (十九)

文章目录 Day13 C 时间库和线程库学习笔记&#xff08;Chrono 与 Thread&#xff09;一、时间库 <chrono>1.1 基本概念1.2 使用示例1.3 duration 字面量单位 二、线程库 <thread>2.1 基本用法2.2 数据竞争&#xff08;Race Condition&#xff09;2.3 加锁&#xff…

C++初阶-C++的讲解1

目录 1.缺省(sheng)参数 2.函数重载 3.引用 3.1引用的概念和定义 3.2引用的特性 3.3引用的使用 3.4const引用 3.5.指针和引用的关系 4.nullptr 5.总结 1.缺省(sheng)参数 &#xff08;1&#xff09;缺省参数是声明或定义是为函数的参数指定一个缺省值。在调用该函数是…

Redisson 实现分布式锁

在平常的开发工作中&#xff0c;我们经常会用到锁&#xff0c;那么锁有什么用呢&#xff1f;锁主要是控制对共享资源的访问顺序&#xff0c;防止多个线程并发操作导致数据不一致的问题。经常可能会听到乐观锁、悲观锁、分布式锁、行锁、表锁等等&#xff0c;那么我们今天总结下…

环境—Ubuntu24(py3.12)安装streamlit(虚拟环境py3.9)

请尽可能不用Ubuntu24请直接跳7.查看解决方案 Action Log 在Ubuntu 24.04中更换为清华源的步骤【Bug】Python 3.12 on Ubuntu 24.04 is Externally Managed - PIP is broken 相关解决方案 从 Ubuntu 24.04 开始&#xff0c;有两个选项&#xff1a; 1. install python pacakg…

【C++进阶】关联容器:set类型

目录 一、set 基本概念 1.1 定义与特点 1.2 头文件与声明 1.3 核心特性解析 二、set 底层实现 2.1 红黑树简介 2.2 红黑树在 set 中的应用 三、set 常用操作 3.1 插入元素 3.2 删除元素 3.3 查找元素 3.4 遍历元素 3.5 性能特征 四、set 高级应用 4.1 自定义比较…

[漏洞篇]SSRF漏洞详解

[漏洞篇]SSRF漏洞详解 免责声明&#xff1a; 本文主要讲解漏洞原理&#xff0c;以及防御手段&#xff0c;旨在帮助大家更好的了解漏洞危害&#xff0c;以及开发中所需要的点&#xff0c;切勿拿来做违法事情&#xff0c;否则后果自负。 一、介绍 概念 SSRF&#xff1a;服务端请…

nuscenes数据集分析

nuscenes数据集分析 标注与总体介绍 nuscenes包含有相机、激光雷达、毫米波雷达、IMU与GPS等设备提供的数据。它的数据采集了1000个场景&#xff0c;每个场景大约有20s&#xff0c;针对目标检测任务&#xff0c;对23类物体进行标注&#xff0c;且以2Hz的频率提供精确的三维目标…

JavaScript学习教程,从入门到精通,JavaScript 运算符及语法知识点详解(8)

JavaScript 运算符及语法知识点详解 一、JavaScript 运算符 1. 算术运算符 用于执行数学运算&#xff1a; 加法- 减法* 乘法/ 除法% 取模&#xff08;余数&#xff09; 递增-- 递减** 幂运算&#xff08;ES6&#xff09; let a 10, b 3; console.log(a b); // 13 conso…

Shell脚本的学习

编写脚本文件 定义以开头&#xff1a;#!/bin/bash #!用来声明脚本由什么shell解释&#xff0c;否则使用默认shel 第一步&#xff1a;编写脚本文件 #!/bin/bash #注释 echo "这是输出" 第二步&#xff1a;加上执行权限&#xff1a;chmod x 脚本文件名.sh 第三步&…

在线PDF文件拆分工具,小白工具功能实用操作简单,无需安装的文档处理工具

小白工具中的在线 PDF 文件拆分工具是一款功能实用、操作便捷的文档处理工具&#xff0c;以下是其具体介绍&#xff1a; 操作流程 上传 PDF 文档&#xff1a;打开小白工具在线PDF文件拆分工具 - 快速、免费拆分PDF文档 - 小白工具的在线 PDF 文件拆分页面&#xff0c;通过点击 …