题面:
题意:
给了一张图,一开始每个点在自己的集合中,如果存在一条边,其两个端点在不同集合中,我们认为这两个集合相邻。
每次选取一个点,若该点在自己的集合中,将该集合所有相邻的集合中间的点并入该集合中
输出q此操作后每个点所在的集合。
化简题意:
给一个 n 个点的 Graph,第 i 个点一刚开始是第 I 种颜色,接着有 k 次操作,第 i 次操作有个参数 oi 代表颜色 oi 会侵略所有和自己相邻的颜色,于是所有和 oi 相邻的颜色全都变成 oi (若已没有颜色oi 已被侵略,则该次操作无效),求最终每个点的颜色。
题解:
我们用并查集维护每个点所属的集合。
用链表维护每个集合有可能往外扩展的点,合并时,遍历这些有可能往外扩展的点,看看哪一些可以扩展,同时维护链表。链表的splice可以O(1)合并两个链表。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<set>
#include<list>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
//#define len(x) (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=998244353;
const double eps=1e-1;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=800100;
const int maxp=1100;
const int maxm=500100;
const int up=100000;
int head[maxn],ver[maxn<<1],nt[maxn<<1],tot=1;
int f[maxn];
list<int>li[maxn];
vector<int>vc;
void add(int x,int y)
{
ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}
int fi(int x)
{
if(f[x]!=x)
f[x]=fi(f[x]);
return f[x];
}
int main(void)
{
int tt;
scanf("%d",&tt);
while(tt--)
{
int n,m,x,y;
scanf("%d%d",&n,&m);
tot=1;
for(int i=1;i<=n;i++)
{
head[i]=0;
li[i].clear();
li[i].pb(i);
f[i]=i;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
x++,y++;
add(x,y);
add(y,x);
}
int q;
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&x);
x++;
if(x!=fi(x)) continue;
vc.clear();
for(auto it=li[x].begin();it!=li[x].end();it++)
{
for(int j=head[*it];j;j=nt[j])
{
int y=fi(ver[j]);
if(y==x) continue;
f[y]=x;
vc.pb(y);
}
}
li[x].clear();
for(int j=0;j<vc.size();j++)
{
if(!li[vc[j]].empty())
li[x].splice(li[x].end(),li[vc[j]]);
}
}
for(int i=1;i<=n;i++)
printf("%d ",fi(i)-1);
putchar('\n');
}
return 0;
}