题目描述:

小明正在做一个网络实验。

他设置了 n 台电脑,称为节点,用于收发和存储数据。

初始时,所有节点都是独立的,不存在任何连接。

小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。

两个节点如果存在网线连接,称为相邻。

小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。

所有发送和接收的节点都会将信息存储下来。

一条信息只存储一次。

给出小明连接和测试的过程,请计算出每个节点存储信息的大小。

输入格式:

输入的第一行包含两个整数 n,m,分别表示节点数量和操作数量。

节点从 1 至 n 编号。

接下来 m 行,每行三个整数,表示一个操作。

  • 如果操作为 1 a b,表示将节点 a 和节点 b 通过网线连接起来。当 a = b 时,表示连接了一个自环,对网络没有实质影响。
  • 如果操作为 2 p t,表示在节点 p 上发送一条大小为 t 的信息。

输出格式:

输出一行,包含 n 个整数,相邻整数之间用一个空格分割,依次表示进行完上述操作后节点 1 至节点 n 上存储信息的大小。

数据范围:

1≤n≤10000
1≤m≤1e5
1≤t≤100

输入样例1:

4 8
1 1 2
2 1 10
2 3 5
1 4 1
2 2 2
1 1 2
1 2 4
2 2 1

输出样例1:

13 13 5 3

分析步骤:

  第一:这道题目是要确定哪些电脑是连接到了一起,哪些电脑是在同一个集合之中,所以应该运用并查集,将其要连接的合并;我们要计算到底储存了多少的信息,其实只要看这个点和根节点的距离到底有多少,如果是根节点的话则直接输出他的值,否则应该输出自身的值加上根节点的值

 第二:书写主函数,构建整体框架:

             将值输入,并查集数组更新自身,将自己设为自己的根节点。

             进行判断如果操作是1的话则,去查找a , b 的父节点,如果a的父节点 != b的父节点 那么就将 d[a] 的值减去d[b]的值。它的作用是在合并操作中更新节点 a 的距离。由于在并查集合并时,将集合 a 合并到集合 b 中,所以要更新集合 a 中所有节点的距离,使其相对于根节点的距离保持一致。因此,这行代码的目的是将集合 a 中的每个节点的距离减去集合 b 的距离,以实现更新。这样,在输出结果时才能得到正确的距离信息。最后将a的父节点改为b。

             进行判断如果操作是2的话则,去将a的集合根节点加上b。

             用for循环判断如果是根节点则直接输出他的值,否则则输出自身的值加上根节点的距离


int main()
{
    scanf("%d%d", &n, &m);  // 读取节点数和操作数
    for (int i = 1; i <= n; i ++ ) p[i] = i;  // 初始化并查集的父节点为自身
    while (m -- )
    {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);  // 读取操作类型和操作数
        if (t == 1)
        {
            a = find(a), b = find(b);  // 查找a和b所属的集合的根节点
            if (a != b)  // 如果a和b不在同一个集合中
            {
                d[a] -= d[b];  // 更新a的距离
                p[a] = b;  // 将a所属的集合合并到b所属的集合中
            }
        }
        else
        {
            a = find(a);  // 查找a所属的集合的根节点
            d[a] += b;  // 更新a所属集合中的所有节点的距离(距离增加b)
        }
    }
    for (int i = 1; i <= n; i ++ )
        if ( i == find(i) ) printf("%d ", d[i]);  // 输出根节点的距离
        else printf("%d ", d[i] + d[find(i)]);  // 输出非根节点的距离(距离需要累加)
    puts("");

    return 0;
}

  第四:书写find函数

              判断x点的父节点是否是自身,不是的话就不断的向上递归去找寻真正的根节点(也就是最祖宗的那个节点)进行路径压缩

// 并查集的查找函数
int find(int x)
{
    if (p[x] == x || p[p[x]] == p[x]) return p[x];  // 如果x是根节点或者x的父节点是根节点,则返回x
    int r = find(p[x]);  // 递归查找x的根节点
    // 路径压缩,将x的父节点更新为根节点,同时更新距离
    d[x] += d[p[x]];
    p[x] = r;
    return r;
}

        

  第五:这是我给大家画的样例图片,大家可以好好照着理解一下,理解一下什么是路径压缩,他是运用递归的思想,不断向上去寻找最根节点的根节点在哪里 , 找到了之后我们就依次返回,最终将路径上的每个点的父节点都变为根节点 

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n, m;
int p[N], d[N];

// 并查集的查找函数
int find(int x)
{
    if (p[x] == x || p[p[x]] == p[x]) return p[x];  // 如果x是根节点或者x的父节点是根节点,则返回x
    int r = find(p[x]);  // 递归查找x的根节点
    // 路径压缩,将x的父节点更新为根节点,同时更新距离
    d[x] += d[p[x]];
    p[x] = r;
    return r;
}

int main()
{
    scanf("%d%d", &n, &m);  // 读取节点数和操作数
    for (int i = 1; i <= n; i ++ ) p[i] = i;  // 初始化并查集的父节点为自身
    while (m -- )
    {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);  // 读取操作类型和操作数
        if (t == 1)
        {
            a = find(a), b = find(b);  // 查找a和b所属的集合的根节点
            if (a != b)  // 如果a和b不在同一个集合中
            {
                d[a] -= d[b];  // 更新a的距离
                p[a] = b;  // 将a所属的集合合并到b所属的集合中
            }
        }
        else
        {
            a = find(a);  // 查找a所属的集合的根节点
            d[a] += b;  // 更新a所属集合中的所有节点的距离(距离增加b)
        }
    }
    for (int i = 1; i <= n; i ++ )
        if ( i == find(i) ) printf("%d ", d[i]);  // 输出根节点的距离
        else printf("%d ", d[i] + d[find(i)]);  // 输出非根节点的距离(距离需要累加)
    puts("");

    return 0;
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐