题目描述:
给定一个包含 n个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b
,在点 a 和点 b 之间连一条边,a 和 b 可能相等;Q1 a b
,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;Q2 a
,询问点 a 所在连通块中点的数量;
输入格式:
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b
,Q1 a b
或 Q2 a
中的一种。
输出格式:
对于每个询问指令 Q1 a b
,如果 aa 和 bb 在同一个连通块中,则输出 Yes
,否则输出 No
。
对于每个询问指令 Q2 a
,输出一个整数表示点 a 所在连通块中点的数量
每个结果占一行。
数据范围:
1≤n,m≤10^5
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
思路:本题为查并集的模板,主要围绕查并集的基本操作。
集合初始化:将每个节点的祖宗节点等于他本身,初始联通点的大小为1。
for(int i=1;i<=n;i++)
{
v[i]=i;num[i]=1;
}
寻找祖宗节点:由于只有只有祖宗节点的p[x]值等于他自己,当该节点不是祖宗节点时要向上寻找他的祖宗节点。同时进行路径压缩,将所有与根节点相连的节点变为叶子节点。
int find_(int x)
{
if(v[x]!=x) v[x]=find_(v[x]);
/*
可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:
p[x]=x;
*/
return v[x];//找到了便返回祖宗节点的值
}
集合合并:将右节点的祖宗节点赋为左节点的祖宗节点。同时更新右节点联通集的大小,即右=右+左。
void merge_(int l,int r)
{
int x=find_(l),y=find_(r);
v[x]=y;
num[y]+=num[x];
}
判断是否在同一联通集:看他们的祖宗节点是否相等。
int ask(int l,int r)
{
return find_(l)==find_(r);
}
代码如下:
#include <bits/stdc++.h>
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define endl '\n'
#define N 100005
using namespace std;
const int inf=0x3f3f3f3f;
const double ex=1e-7;
const int mod=1e9+7;
int gcd(int a ,int b){ return b ? gcd(b,a%b) : a ;}
string st;
int v[N],num[N];
int find_(int x)
{
if(v[x]!=x) v[x]=find_(v[x]);
return v[x];
}
int ask(int l,int r)
{
return find_(l)==find_(r);
}
void merge_(int l,int r)
{
int x=find_(l),y=find_(r);
v[x]=y;
num[y]+=num[x];
}
signed main()
{ios
int n,m,l,r,k;
string op;
cin >>n>>m;
for(int i=1;i<=n;i++)
{
v[i]=i;num[i]=1;
}
while(m--)
{
cin >>op;
if(op=="C")
{
cin >>l>>r;
if(!ask(l,r))
{
merge_(l,r);
}
}else if(op=="Q1")
{
cin >>l>>r;
if(ask(l,r))
{
cout <<"Yes"<<endl;
}else{
cout <<"No"<<endl;
}
}else{
cin >>k;
int ans=find_(k);
cout <<num[ans]<<endl;
}
}
return 0;
}