当前位置:   article > 正文

并查集(Union-Find Disjoint Sets/DSU)_奶酪并查集

奶酪并查集

目录

 一:明确概念

1.什么是并查集

2.并查集相关过程

3.并查集相关代码

初始化部分代码

查询部分代码

合并部分代码

二:题目讲解

P3367 【模板】并查集

P1536 村村通

P3958 [NOIP2017 提高组] 奶酪


本文题目传送门:

【模板】并查集 - 洛谷

村村通 - 洛谷

[NOIP2017 提高组] 奶酪 - 洛谷


 一:明确概念

1.什么是并查集

        并(unity)查(find)集(set),即可以合并、查找的树状数据结构,常用于处理不交集查询问题。

        下面是一个典型例子:

图1

        图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.并查集相关代码

        下面是并查集通用代码。

初始化部分代码
  1. void init(int n)
  2. {
  3. for(int i=1; i<=n; ++i)
  4. {
  5. father[i]=i;
  6. //最开始,每个人只和自己是直接认识的,也就是说自己是自己的老大
  7. }
  8. }
查询部分代码
  1. int find(int x)
  2. {
  3. //如果x是自己的同伴,那x就是这个队伍的老大,返回其本身即可。
  4. if(father[x]==x)
  5. {
  6. return x;
  7. }
  8. //否则就继续寻找x的老大father[x],看father[x]的老大是谁
  9. else
  10. {
  11. return find(father[x]);
  12. }
  13. }
合并部分代码
  1. //合并x和y所在的队伍
  2. int combine(int x,int y)
  3. {
  4. //p为x所在队伍的老大
  5. //q为y所在队伍的老大
  6. int p=find(x);
  7. int q=find(y);
  8. if(p==q)
  9. {
  10. //两者在同一个队伍中
  11. return 0;
  12. }
  13. //将x所在的队伍p合并到q的队伍中
  14. else
  15. {
  16. father[p]=q;
  17. return 1;
  18. }
  19. }

二:题目讲解

题目链接在最前面        

P3367 【模板】并查集

        这道题说白了就是要判断两个学生的老大是不是同一个人,其实很简单,只需要用到这个

if(find(x)==find(y))

        但是注意,一个人只能有一个老大,所以当有a已经拥有老大s,而来做b的小弟时,s成为b的小弟。

        上代码!

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int n,m,father[10005];
  4. int find(int x)//这个函数前面说到过
  5. {
  6. if(father[x]==x) return x;
  7. return father[x]=find(father[x]);
  8. }
  9. void combine(int x,int y)//这个也一样
  10. {
  11. int fax=find(x),fay=find(y);
  12. if(fax!=fay)
  13. {
  14. father[fax]=fay;
  15. }
  16. }
  17. int main()
  18. {
  19. cin>>n>>m;
  20. for(int i=1; i<=n; i++)
  21. {
  22. father[i]=i;
  23. }
  24. for(int i=1; i<=m; i++)
  25. {
  26. int x,y,z;
  27. cin>>z>>x>>y;
  28. if(z==1)
  29. {
  30. combine(x,y);
  31. }
  32. else
  33. {
  34. if(find(x)==find(y))
  35. {
  36. cout<<"Y"<<endl;
  37. }
  38. else
  39. {
  40. cout<<"N"<<endl;
  41. }
  42. }
  43. }
  44. return 0;
  45. }

        小试牛刀一下,很简单对吧QWQ

P1536 村村通

        这道题,我们没输入两个村庄,就把它们连起来。如果遇到没有老大的村庄(祖先是没有老大的),就是遇到了一个需要建设的村庄。

        注意:最终答案要-1

        猜猜为啥?三个点,做成连通图,要几条线?两条对不对。四个点就要三条线,所以最终答案-1。

        上代码!

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int fa[1000001], n, m, x, y;
  4. int find(int x)
  5. {
  6. if(x!=fa[x])//当x不等于它的老大时(当它是祖先时,它没有老大)
  7. {
  8. fa[x]=find(fa[x]);//继续找他的老大的老大
  9. }
  10. return fa[x];//返回祖先
  11. }
  12. void combine(int x, int y)
  13. {
  14. int r1=find(x);//寻找祖先
  15. int r2=find(y);
  16. fa[r1]=r2;//祖先和祖先结为老大(谁是老大谁是儿子都可以)
  17. }
  18. int main()
  19. {
  20. while(1)
  21. {
  22. int ans=0;
  23. cin>>n;
  24. if(n==0)
  25. {
  26. return 0;
  27. }
  28. cin>>m;
  29. for(int i=1; i<=n; i++)//初始化
  30. {
  31. fa[i]=i;
  32. }
  33. for(int i=1; i<=m; i++)
  34. {
  35. cin>>x>>y;
  36. combine(x, y);
  37. }
  38. for(int i=1; i<=n; i++)
  39. {
  40. if(find(i)==i)//自己的老大就是自己本身
  41. {
  42. ans++;
  43. }
  44. }
  45. printf("%d\n", ans - 1);
  46. }
  47. return 0;
  48. }
P3958 [NOIP2017 提高组] 奶酪

         这道题思路很简单。两个洞如果相交或相切,就编入同一个队伍中,一个队伍就是一个通道。只需要判断每个通道是否存在元素与底部、顶部连接即可。

        那怎么判断两个圆是否相交呢?如果两个圆半径之和大于或等于两个圆圆心的距离,那么两个圆相交。

        上代码!

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int father[1001];
  4. int find(int x)
  5. {
  6. if (x!=father[x]) father[x]=find(father[x]);
  7. return father[x];
  8. }
  9. long long dis(long long x,long long y,long long z,long long x1,long long y1,long long z1)
  10. {
  11. return (x-x1)*(x-x1)+(y-y1)*(y-y1)+(z-z1)*(z-z1);
  12. }//两点距离公式
  13. long long x[100001],y[100001],z[100001];
  14. int f1[100001],f2[100001];//f1记录与顶面相交的洞的序号,f2记录与底面相交的洞的序号
  15. int main(){
  16. int t;
  17. cin>>t;
  18. int n,h;
  19. long long r;
  20. for(int i=1;i<=t;i++)
  21. {
  22. cin>>n>>h>>r;
  23. int tot1=0;//记录与顶面相交的洞有几个
  24. int tot2=0;//记录与底面相交的洞有几个
  25. for(int j=1;j<=n;j++)
  26. {
  27. father[j]=j; //并查集初始化
  28. }
  29. for (int j=1;j<=n;j++)
  30. {
  31. cin>>x[j]>>y[j]>>z[j];
  32. if(z[j]+r>=h)
  33. {
  34. tot1++;
  35. f1[tot1]=j;
  36. }
  37. if(z[j]-r<=0)
  38. {
  39. tot2++;
  40. f2[tot2]=j;
  41. }
  42. for (int k=1;k<=j;k++)//枚举之前的洞是否与这个洞相交,如果相交则合并集合
  43. {
  44. if ((x[j]-x[k])*(x[j]-x[k])+(y[j]-y[k])*(y[j]-y[k])>4*r*r) continue;//防止爆long long的特判。
  45. if (dis(x[j],y[j],z[j],x[k],y[k],z[k])<=4*r*r)
  46. {
  47. int a1=find(j);
  48. int a2=find(k);
  49. if (a1!=a2) father[a1]=a2;
  50. }
  51. }
  52. }
  53. int s=0;
  54. //看看每一个中是否有洞连接上下面
  55. for(int j=1;j<=tot1;j++)
  56. {
  57. for(int k=1;k<=tot2;k++)
  58. {
  59. if(find(f1[j])==find(f2[k]))
  60. {
  61. s=1;
  62. break;
  63. }
  64. }
  65. if(s==1) break;
  66. }
  67. if(s==1) cout<<"Yes"<<endl;
  68. else cout<<"No"<<endl;
  69. }
  70. return 0;
  71. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/792348
推荐阅读
相关标签
  

闽ICP备14008679号