亲戚
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实很不容易,现在给出某个亲戚关系
图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲
戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数 n,m,p,(n<=5000,m<=5000,p<=5000),
分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi,Mj,1<=Mi,Mj<=N,
表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
P 行,每行一个“Yes”或“No”。
表示第 i 个询问的答案为“具有”或“不具有”亲戚关系。
样例输入:
6 5 3
1 2
1 5
6 4
5 2
1 3
1 4
2 3
5 6
样例输出:
Yes
Yes
No
把这个问题抽象成集合,每个有亲戚关系的人组成一个帮派,然后有亲戚关系的人合并成一家,还有查询两人是否有亲戚关系。往往遇见这种集合合并查询问题,直接用并查集
并查集是一种树型数据结构,擅长处理集合合并为不相交集后的查询问题 (该数据结构用普通数组即可实现) |
基本步骤:
考虑一个极端情况,这一个家族亲戚关系排成了一个链,那查询一次的时间复杂度趋近与O(n)。所以,我们要进行一个叫做 路径压缩 的东西,就是把所有人的辈分都磨平,一个家族的人都拥有一个共同的并且只有一个的“爹”
,抽象成树形结构就是一棵树只有一个根节点,高度为2,剩下全是叶子结点
然后,并查集就趋近于常数级,≈O(4)(下面的了解一下,反正我是看不懂)
1975 年 Tarjan 证明,若将路径压缩与按秩归并合联合使用,n 次操作需要花费 O(n·α(n))
时间。α(n)是单变量阿克曼函数的逆函数,它是一个增长速度比 logn 慢的多但又不是常数
的函数,在一般情况下α(n)≤4,可以当作常数看
#include
#include
#include
#include
#include
#include
#include
#include
#define TIE ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define N 5010
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int father[N],n,m,p;
int Find(int x){
if(x==father[x]) return x;
return father[x]=Find(father[x]);
}
int main() {
TIE;cin>>n>>m>>p;
for(int i=1;i<=n;++i) father[i]=i;
for(int i=1;i<=m;++i){
int x,y;
cin>>x>>y;
father[Find(x)]=Find(y);
}
while(p--){
int x,y;
cin>>x>>y;
if(Find(x)==Find(y)) cout<<"Yes"<<"\n";
else cout<<"No"<<"\n";
}
return 0;
}
本题的代码就是并查集最基本的模板~~~
信息传递
题目描述
有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定
的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生
日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人
只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,
游戏结束。请问该游戏一共可以进行几轮?
输入描述
共 2 行。
第 1 行包含 1 个正整数 n,表示 n 个人。
第 2 行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn,
其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学,
Ti≤n 且 Ti≠i。
输出描述
1 个整数,表示游戏一共可以进行多少轮。
对于 30%的数据, n ≤ 200;
对于 60%的数据,n ≤ 2500;
对于 100%的数据,n ≤ 200000。
样例输入
5
2 4 2 3 1
样例输出
3
本题其实就是求解给定的图中所有环中的最小环的边数,但怎么求呢?
提供一种思路:我们维护一个每个点到代表元的距离的数组
为了保持并查集的优秀复杂度,我们当然得在Find()函数和Union()函数上下手
观察 Find()函数的路径压缩:
从 x 出发向 root 递归的过程中,我们保存了 x 到 root 的所有沿途节点。
在 root 向 x 回溯的过程中,我们更新了所有的 father 值。
同样地,在这个过程中,我们可同时更新沿途节点到 root 的距离
所以我们的函数们变成了这样:
int Find(int x){
if(x==father[x]) return x;
int root=Find(father[x]);//记得这里是找它的父亲
dis[x]+=dis[father[x]];
return father[x]=root;
}
void Union(int x,int y){
int xx=Find(x);//这里因为只是比较两个元素的代表元是否相同,原点因为还得求值,
int yy=Find(y);//所以还得保留,不能覆盖
if(xx==yy) ans=min(ans,dis[x]+dis[y]+1);//这个是此题的骚操作,不是模板!!!
father[xx]=yy;//这里也是
dis[x]=dis[y]+1;
}
于是乎,带点上面这种骚操作的并查集就是边带权并查集
先打住,以读者你们这些dalao的联想能力,应该能想到,有边带权,那必定有点带权呀!!!
家族合并
题目描述
有 n 个人,刚开始每个人都代表着一个家族,现在要对其进行操作,一共有如下三种操作:
1: C a b,a 和 b 所在的家族合并到一起
2:Q1 a b,查询 a 和 b 是否在同一个家族
3:Q2 a,查询 a 所在的家族有多少个人
输入描述
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。
输出描述
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个家族中,则输出 Yes,否则输出 No。
对于每个询问指令 Q2 a,输出一个整数表示点 a 所在家族的人数
每个结果占一行。
输入样例
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例
Yes
2
3
数据描述
1≤n,m≤105
这个题目就是简单的并查集模板加上了一个操作:记录这个集合中的所有元素,很简单,还是把Find()和Union()改一下就行了
#include
#include
#include
#include
#include
#include
#include
#include
#define TIE ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define N 100010
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int father[N],n,m,p,num[N];
int Find(int x){
if(x==father[x]) return x;
return father[x]=Find(father[x]);
}
void Union(int x,int y){
x=Find(x);
y=Find(y);
father[x]=y;
num[y]+=num[x];
}
int main() {
TIE;cin>>n>>m;
for(int i=1;i<=n;++i){
father[i]=i;
num[i]=1;
}
for(int i=1;i<=m;++i){
int x,y;
string re;
cin>>re;
if(re=="C"){
cin>>x>>y;
if(Find(x)!=Find(y)) Union(x,y);
}
else if(re=="Q1"){
cin>>x>>y;
if(Find(x)==Find(y)) cout<<"Yes"<<"\n";
else cout<<"No"<<"\n";
}else{
cin>>x;
cout<<num[Find(x)]<<"\n";
}
}
return 0;
}
题目传送门
解决这种拥有多种关系的并查集问题,就要用到种类并查集,其实很简单就是比如说有种关系:敌人的敌人是朋友! 我要必须要存朋友和敌人这两种关系,所以我们在合并时要开两倍空间。这就是种类并查集的不同:有多少种关系,开几倍空间
本题就是是一种循环吃的一种状态,所以如果是同类的话,把他们的猎物和天敌都设为同类 如果是x吃y就把x的天敌和y的猎物设为同类,x的猎物和y设为同类,x和y的天敌设为同类#include
#include
#include
#include
#include
#include
#include
#include
#define TIE ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define N 150010
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,k,f[N],ans;
int Find(int x){
if(x==f[x]) return x;
return f[x]=Find(f[x]);
}
void Union(int x,int y){
int xx=Find(x),yy=Find(y);
f[xx]=yy;
}
int main() {
TIE;cin>>n>>k;
for(int i=1;i<=3*n;++i) f[i]=i;
for(int i=1;i<=k;++i){
int a,x,y;
cin>>a>>x>>y;
if(x>n||y>n){ans++;continue;}
if(a==1){
if(Find(x+n)==Find(y)||Find(x+2*n)==Find(y)){ans++;continue;}
Union(x,y),Union(x+n,y+n),Union(x+2*n,y+2*n);
}else{
if(Find(x)==Find(y)||Find(x+2*n)==Find(y)){ans++;continue;}
Union(x+n,y),Union(x,y+2*n),Union(x+2*n,y+n);
}
}
cout<<ans<<"\n";
return 0;
}