并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。

并查集支持两种操作:
合并(Union):合并两个元素所属集合(合并对应的树)
查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合

(优化)路径压缩

1
2
3
4
5
int find(int u)
{
if(f[u]==u) return u;
else return f[u]=find(f[u]);
}

种类并查集


带权并查集

典!

Monkeys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//正难则反
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =4e5+10;

int a[N][3],ans[N],nxt[N], p[N],w[N],vis[N][3],fa[N],head[N],tail[N];

int find(int x) //找祖先
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}

void merge(int u,int v,int p)
{
int fu=find(u);
int fv=find(v);
if(fu==fv) return;
if(fu>fv) swap(fu,fv); //fu小
if(fu==1 && p!=-1) //p=-1代表没删过的边,p!=-1则更新答案
{
for(int use=head[fv];use;use=nxt[use]) ans[use]=p;
}
fa[fv]=fu; //fv连接到fu
nxt[tail[fu]]=head[fv]; //fu最后一个点的nxt=fv开头
tail[fu]=tail[fv]; //更新fu最后一点
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);

int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i][1]>>a[i][2]; //a[i][1/2]第i条边连接的左/右边
ans[i]=-1;
}
for(int i=0;i<m;i++)
{
cin>>p[i]>>w[i];
vis[p[i]][w[i]]=1; //记录此边被删:v[i][j] 第i条边连接的左/右边
}
for(int i=1;i<=n;i++)
{
fa[i]=head[i]=tail[i]=i;
nxt[i]=0;
}
for(int i=1;i<=n;i++)
{
if(!vis[i][1] && a[i][1]!=-1) merge(i,a[i][1],-1);
if(!vis[i][2] && a[i][2]!=-1) merge(i,a[i][2],-1);
}
for(int i=m-1;i>=0;i--)
{
int u=p[i],v=a[p[i]][w[i]];
merge(u,v,i);
}
for(int i=1;i<=n;i++)
{
cout<<ans[i]<<endl;
}
return 0;
}