题目链接:点击查看
题目大意:给出一个由 n 个点和 m 条边组成的无向图,每个点初始时都有颜色 i ,i ∈ [ 0 , n - 1 ] ,接下来有 q 次操作,每次操作会给出一种颜色 x ,分两种情况讨论:
- 如果颜色为 x 的点不存在,则跳过此次操作
- 如果颜色为 x 的点存在,则将颜色为 x 的点的相邻的所有颜色块,都染成 x 的颜色
问最后每个点的颜色是什么
题目分析:合并问题不难想到并查集,对于每个点的颜色可以用并查集来维护,不过如果暴力更新的话肯定会 T ,有一个比较显然且比较重要的结论就是,每个点至多被遍历一次,因为如果当某个点被遍历过一次后,那么其相邻的所有点肯定和当前的点都变为同一个颜色了,接下来无论如何操作,再遍历这个点肯定是没有任何进展的了,所以基于这个性质,我们可以用链表配合并查集实现模拟
首先并查集的 f 数组配合 find 函数用来维护每个点被染成的颜色,初始化为每个点本身,其次每个颜色都建立一个链表,用来储存当前颜色下有多少个点被染成了相同的颜色
对于每一个询问的颜色 x ,如果不存在的话,直接跳过即可,如果存在的话,可以遍历一遍相应的链表进行扩展,根据上面的性质可知,遍历过的元素直接删除掉即可,对于新加入的元素,可以利用链表的 splice 方法,这个方法可以 O( 1 ) 合并两个链表,如此一来,整体的时间复杂度也只是 O( n * a + m ),a 是并查集的时间开销
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<list>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=8e5+100;
list<int>li[N];
vector<int>node[N];
int f[N];
void init(int n)
{
for(int i=0;i<n;i++)
{
f[i]=i;
li[i].clear();
li[i].push_back(i);
node[i].clear();
}
}
int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
void merge(int x,int y)//xx合并到yy
{
int xx=find(x);
int yy=find(y);
if(xx!=yy)
{
f[xx]=yy;
li[yy].splice(li[yy].end(),li[xx]);
}
}
void update(int x)
{
if(x!=find(x))
return;
int sz=li[x].size();
while(sz--)
{
int u=li[x].front();
li[x].pop_front();
for(auto v:node[u])
merge(v,u);
}
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int w;
cin>>w;
while(w--)
{
int n,m;
scanf("%d%d",&n,&m);
init(n);
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
node[u].push_back(v);
node[v].push_back(u);
}
int q;
scanf("%d",&q);
while(q--)
{
int x;
scanf("%d",&x);
update(x);
}
for(int i=0;i<n;i++)
printf("%d ",find(i));
puts("");
}
return 0;
}