繁忙的都市

70 阅读3分钟

线上OJ:

1392:繁忙的都市(city)

核心思想:

本题本质是最小生成树,可以采用 Prim 和 Kruskal 算法来解。

解法一、Prim

Prim 算法:蓝白点
阶段0:初始化(minv[], vis[], minv[1])
阶段1:在剩余蓝点中找出 minv 最小的点 k
阶段2:由于k点是新加入的白点,所以要更新剩余蓝点到白点的 minv,以备下一轮使用

#include <bits/stdc++.h>
using namespace std;

const int N = 305;

int n, m, e[N][N], minv[N], maxv = -1;  // minv[i] 存储节点i与白点相连的最小权值; e[i][j]存放i->j的边权
bool vis[N];  // vis[i] = true 表示i已经访问并加入最小生成树(true说明已经变成白点,false说明还是蓝点)

int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)  // 读入条边
    {
        int x, y, v;
        scanf("%d %d %d", &x, &y, &v);
        e[x][y] = e[y][x] = v;
    }

    memset(minv, 0x3f, sizeof(minv));
    memset(vis, 0, sizeof(vis));

    minv[1] = 0;  // Prim算法以1为起点生成最小生成树,所以1到自己的最小权值为0

    // Prim 算法主体
    for(int i = 1; i <= n; i++)  // 外循环 n 次(n个点)
    {
        int k = 0;
        // 阶段一:在剩余蓝点中找出 minv 最小的点
        for(int j = 1; j <= n; j++)
            if( !vis[j] && (minv[j] < minv[k]) )  k = j;

        vis[k] = true; // 转为白点
        // 阶段二:由于k点是新的白点,所以要更新剩余蓝点到白点的 minv
        for(int j = 1; j <= n; j++)
            if( !vis[j] && (e[k][j] != 0) && (e[k][j] < minv[j]) )
                minv[j] = e[k][j];
    }

    for(int i = 1; i <= n; i++)  maxv = max(maxv, minv[i]);

    printf("%d %d\n", n - 1, maxv);
    return 0;
}

解法二、 Kruskal

Kruskal 算法:每一轮找边权最小的,且端点根节点不同的,将其合并(并查集)
step1、根据边权v,对边进行升序排序
step2、for循环边,如果边的两个端点分属不同阵营,则合并
step3、合并一次就是找到一条边,当找到 n-1 条边时,结束循环

#include <bits/stdc++.h>
using namespace std;

const int N = 305;

struct Node
{
    int x, y, v; // Kruskal可以使用结构体存储边的两端坐标x和y,以及边权v。因为要根据边权v进行排序,并合并边的两个端点x和y
};
Node e[90010];

int n, m, p[N], maxv = -1;  // Kruskal使用的是并查集思想,所以需要p[i] 存储i的根节点;

bool cmp(Node a, Node b)
{
    return a.v < b.v;
}

int find(int x)
{
    if(p[x] != x)  p[x] = find(p[x]);
    return p[x];
}

int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)  // 读入条边
    {
        int x, y, v;
        scanf("%d %d %d", &x, &y, &v);
        e[i] = {x, y, v};
    }

    for(int i = 1; i <= n; i++)  p[i] = i;  // 初始化每个元素的根节点为自己(即每个元素都是独立的)

    // Kruskal核心代码
    sort(e + 1, e + 1 + m, cmp); // 根据边权v,进行升序排序
    int k = 0;
    for(int i = 1; i <= m; i++)  // 总共有m条边,已完成顺序,寻找第一个两边端点分属不同集合的边,将他们合并
    {
        int a = find(e[i].x), b = find(e[i].y);  // 找到边的两个端点的根节点
        if( a != b )  // 如果边的两个端点的根节点在不同的阵营
        {
            p[a] = b;  // 则合并
            maxv = max(maxv, e[i].v);
            k++;
            if( k == n - 1 )  break;
        }
    }

    printf("%d %d\n", n - 1, maxv);
    return 0;
}