目录
本文题目传送门:
一:明确概念
1.什么是并查集
并(unity)查(find)集(set),即可以合并、查找的树状数据结构,常用于处理不交集查询问题。
下面是一个典型例子:
图1中每个箭头两端连接的是两名呈同班同学关系的学生,请问,1号同学和5号同学是同一个班的吗?
我们简化一下,就是如果我们能确定任意两个人之间是不是同班同学,我们能不能确定所有同学之间是不是同班同学。
在简化,在同一个班的学生里,指定一个老大(t),当查询两个学生(x,y)时,只需要看者两个学生是否与老大间接认识或直接认识,如果都认识,那么者两个学生就在同一个班里。
2.并查集相关过程
给出条件:
1号与2号是同学
3号与4号是同学
5号与2号是同学
4号与6号是同学
2号与6号是同学
8号与7号是同学
9号与7号是同学
1号与6号是同学
2号与4号是同学
最开始,所有人的同学都是自己(似乎没毛病)。数组father代表的是每一位同学箭头所指
的同学,也就是与其呈直接认识关系的另一位同学(有点绕)。
下面,我们要让1和2建立同学关系,让2当1的老大。
2已经成功当上了1的老大,我们让4也称为3的老大。
下面我们让6把4和他的小弟收编了,让2成为5的老大。
现在6又把2和他的小弟收编了。
下面8和9投靠了7。
下面1和6建立直接认识关系,2和4页建立直接认识关系。
这就是并查集的基本过程,下面是代码部分。
3.并查集相关代码
下面是并查集通用代码。
初始化部分代码
void init(int n)
{
for(int i=1; i<=n; ++i)
{
father[i]=i;
//最开始,每个人只和自己是直接认识的,也就是说自己是自己的老大
}
}
查询部分代码
int find(int x)
{
//如果x是自己的同伴,那x就是这个队伍的老大,返回其本身即可。
if(father[x]==x)
{
return x;
}
//否则就继续寻找x的老大father[x],看father[x]的老大是谁
else
{
return find(father[x]);
}
}
合并部分代码
//合并x和y所在的队伍
int combine(int x,int y)
{
//p为x所在队伍的老大
//q为y所在队伍的老大
int p=find(x);
int q=find(y);
if(p==q)
{
//两者在同一个队伍中
return 0;
}
//将x所在的队伍p合并到q的队伍中
else
{
father[p]=q;
return 1;
}
}
二:题目讲解
题目链接在最前面
P3367 【模板】并查集
这道题说白了就是要判断两个学生的老大是不是同一个人,其实很简单,只需要用到这个
if(find(x)==find(y))
但是注意,一个人只能有一个老大,所以当有a已经拥有老大s,而来做b的小弟时,s成为b的小弟。
上代码!
#include <bits/stdc++.h>
using namespace std;
int n,m,father[10005];
int find(int x)//这个函数前面说到过
{
if(father[x]==x) return x;
return father[x]=find(father[x]);
}
void combine(int x,int y)//这个也一样
{
int fax=find(x),fay=find(y);
if(fax!=fay)
{
father[fax]=fay;
}
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
{
father[i]=i;
}
for(int i=1; i<=m; i++)
{
int x,y,z;
cin>>z>>x>>y;
if(z==1)
{
combine(x,y);
}
else
{
if(find(x)==find(y))
{
cout<<"Y"<<endl;
}
else
{
cout<<"N"<<endl;
}
}
}
return 0;
}
小试牛刀一下,很简单对吧QWQ
P1536 村村通
这道题,我们没输入两个村庄,就把它们连起来。如果遇到没有老大的村庄(祖先是没有老大的),就是遇到了一个需要建设的村庄。
注意:最终答案要-1
猜猜为啥?三个点,做成连通图,要几条线?两条对不对。四个点就要三条线,所以最终答案-1。
上代码!
#include <bits/stdc++.h>
using namespace std;
int fa[1000001], n, m, x, y;
int find(int x)
{
if(x!=fa[x])//当x不等于它的老大时(当它是祖先时,它没有老大)
{
fa[x]=find(fa[x]);//继续找他的老大的老大
}
return fa[x];//返回祖先
}
void combine(int x, int y)
{
int r1=find(x);//寻找祖先
int r2=find(y);
fa[r1]=r2;//祖先和祖先结为老大(谁是老大谁是儿子都可以)
}
int main()
{
while(1)
{
int ans=0;
cin>>n;
if(n==0)
{
return 0;
}
cin>>m;
for(int i=1; i<=n; i++)//初始化
{
fa[i]=i;
}
for(int i=1; i<=m; i++)
{
cin>>x>>y;
combine(x, y);
}
for(int i=1; i<=n; i++)
{
if(find(i)==i)//自己的老大就是自己本身
{
ans++;
}
}
printf("%d\n", ans - 1);
}
return 0;
}
P3958 [NOIP2017 提高组] 奶酪
这道题思路很简单。两个洞如果相交或相切,就编入同一个队伍中,一个队伍就是一个通道。只需要判断每个通道是否存在元素与底部、顶部连接即可。
那怎么判断两个圆是否相交呢?如果两个圆半径之和大于或等于两个圆圆心的距离,那么两个圆相交。
上代码!
#include<bits/stdc++.h>
using namespace std;
int father[1001];
int find(int x)
{
if (x!=father[x]) father[x]=find(father[x]);
return father[x];
}
long long dis(long long x,long long y,long long z,long long x1,long long y1,long long z1)
{
return (x-x1)*(x-x1)+(y-y1)*(y-y1)+(z-z1)*(z-z1);
}//两点距离公式
long long x[100001],y[100001],z[100001];
int f1[100001],f2[100001];//f1记录与顶面相交的洞的序号,f2记录与底面相交的洞的序号
int main(){
int t;
cin>>t;
int n,h;
long long r;
for(int i=1;i<=t;i++)
{
cin>>n>>h>>r;
int tot1=0;//记录与顶面相交的洞有几个
int tot2=0;//记录与底面相交的洞有几个
for(int j=1;j<=n;j++)
{
father[j]=j; //并查集初始化
}
for (int j=1;j<=n;j++)
{
cin>>x[j]>>y[j]>>z[j];
if(z[j]+r>=h)
{
tot1++;
f1[tot1]=j;
}
if(z[j]-r<=0)
{
tot2++;
f2[tot2]=j;
}
for (int k=1;k<=j;k++)//枚举之前的洞是否与这个洞相交,如果相交则合并集合
{
if ((x[j]-x[k])*(x[j]-x[k])+(y[j]-y[k])*(y[j]-y[k])>4*r*r) continue;//防止爆long long的特判。
if (dis(x[j],y[j],z[j],x[k],y[k],z[k])<=4*r*r)
{
int a1=find(j);
int a2=find(k);
if (a1!=a2) father[a1]=a2;
}
}
}
int s=0;
//看看每一个中是否有洞连接上下面
for(int j=1;j<=tot1;j++)
{
for(int k=1;k<=tot2;k++)
{
if(find(f1[j])==find(f2[k]))
{
s=1;
break;
}
}
if(s==1) break;
}
if(s==1) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}