Operating on a Graph(并查集,链表)

51 阅读2分钟

G-Operating on a Graph_2023牛客五一集训派对day3 (nowcoder.com)

原题题面

image.png

题目描述

编号为 0n10\sim n-1nn 个点,每个点对应属于其编号的阵营。

另外有 mm 条边,每条边连接两个点。

接下来给出 qq 次询问,每次给出一个阵营的编号(注意不是节点的编号)。我们将属于这个阵营的所有节点,其相连的不属于同阵营的节点归于这个阵营。

最终将 nn 个节点的所属阵营输出。

共有 tt 个样例。

输入输出格式

1t1.6×1051\le t \le 1.6\times10^5

n,m,q8×105n,m,q \le 8\times10^5

题目分析

本题的核心知识点为 并查集链表

首先由本题中阵营这个机制可以明显想到并查集来统计,对于每次找到需要改变阵营的节点,可以利用并查集将其父节点更新, p[x] 意为当前节点所属的阵营。

而由于内存限制,仅从我的解法来看,利用 vector 储存边与边直接的连接会爆内存,所以我们利用链表的机制进行储存,这样每次将阵营节点更换阵营可以直接进行链表的头节点交换而不用开辟多的空间。

接下来就是如何寻找需要更换阵营的节点,首先我们将阵营中的初始节点,即 x==p[x]x==p[x] 的节点 xx 作为父节点(boss)。若需要向外拓展,则第一层为其直接连接的节点,下一层则为节点的相连节点。每次改变一层的节点时,我们可以将父节点(boss)的相连的节点更新为这层节点所连不属于同阵营的节点。

随后按照输入依次向外拓展即可。

Accept代码

#include <bits/stdc++.h>

using namespace std;

const int N = 8e5 + 10;
int p[N];
list<int> g[N];

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

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int t; cin >> t;
    while (t --)
    {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i < n; i ++) p[i] = i, g[i].clear();
        for (int i = 0; i < m; i ++)
        {
            int a, b;
            cin >> a >> b;
            g[a].push_back(b);
            g[b].push_back(a);
        }
        int q; cin >> q;
        while (q --)
        {
            int u; cin >> u;
            list<int> s;
            for (auto x : g[u])
            {
                int y = find(x);
                if (y != u) p[y] = u, s.splice(s.end(), g[y]);
            }
            swap(g[u], s);
        }
        for (int i = 0; i < n; i ++) cout << find(i) << "\n "[i < n - 1];
    }
    return 0;
}