• 【数据结构】—— 并查集



    并查集 

    ①合并两个集合

    ②查询某个元素的祖宗节点

    查找 

    通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。

    在这样的思想下,并查集的查找算法诞生了。

    路径压缩 

    这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我的祖先是谁与我父亲是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。把在路径上的每个节点都直接连接到根上,这就是路径压缩。

    合并 

    宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。
    我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。

     


    ① 记录每个集合的大小 -》 绑定到根节点

    ② 记录每个点到根节点的距离 -》 绑定到每个点身上 


    AcWing 1250. 格子游戏 

    输入样例:

    1. 3 5
    2. 1 1 D
    3. 1 1 R
    4. 1 2 D
    5. 2 1 R
    6. 2 2 D

    输出样例:

    4

    问题等价于:在一次次连边的过程中,什么时候第一次连成一个环 

    可以将其看成是图论中的点跟边,出现环的时候,等价于,两个点在连通之前就已经在同一个连通块当中


    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. const int N = 400010;
    6. int n, m;
    7. int p[N];
    8. // 坐标映射
    9. int get(int x, int y)
    10. {
    11. return x * n + y;
    12. }
    13. int find(int x)
    14. {
    15. if(p[x] != x) p[x] = find(p[x]);
    16. return p[x];
    17. }
    18. int main()
    19. {
    20. cin >> n >> m;
    21. for(int i = 1; i <= n * n; i ++ ) p[i] = i;
    22. int res = 0;
    23. for(int i = 1; i <= m; i ++ )
    24. {
    25. int x, y;
    26. char d;
    27. cin >> x >> y >> d;
    28. x --, y -- ;
    29. int a = get(x, y);
    30. int b;
    31. if(d == 'D') b = get(x + 1, y);
    32. else b = get(x, y + 1);
    33. int pa = find(a), pb = find(b);
    34. if(pa == pb)
    35. {
    36. res = i;
    37. break;
    38. }
    39. p[pa] = pb;
    40. }
    41. if(!res) puts("draw");
    42. else cout << res << endl;
    43. return 0;
    44. }

    AcWing 1252. 搭配购买 

    输入样例:

    1. 5 3 10
    2. 3 10
    3. 3 10
    4. 3 10
    5. 5 100
    6. 10 1
    7. 1 3
    8. 3 2
    9. 4 2

    输出样例:

    1

     把每一个联通块看做是一个物品,然后就变成了一个01 背包问题了。


    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. const int N = 10010;
    6. int n, m, vol;
    7. int v[N], w[N];
    8. int p[N];
    9. int f[N];
    10. int find(int x)
    11. {
    12. if(p[x] != x) p[x] = find(p[x]);
    13. return p[x];
    14. }
    15. int main()
    16. {
    17. cin >> n >> m >> vol;
    18. for(int i = 1; i <= n; i ++ ) p[i] = i;
    19. for(int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    20. while(m -- )
    21. {
    22. int a, b;
    23. cin >> a >> b;
    24. int pa = find(a), pb = find(b);
    25. if(pa != pb)
    26. {
    27. v[pb] += v[pa];
    28. w[pb] += w[pa];
    29. p[pa] = p[pb];
    30. }
    31. }
    32. // 01背包问题
    33. for(int i = 1; i <= n; i ++ )
    34. if(p[i] == i)
    35. for(int j = vol; j >= v[i]; j -- )
    36. f[j] = max(f[j], f[j - v[i]] + w[i]);
    37. cout << f[vol] << endl;
    38. return 0;
    39. }

    AcWing 237. 程序自动分析 

    输入样例:

    1. 2
    2. 2
    3. 1 2 1
    4. 1 2 0
    5. 2
    6. 1 2 1
    7. 2 1 1

    输出样例:

    1. NO
    2. YES

    思路
    1.读入数据并加入离散化数组
    2.离散化后去重,并用此数组的个数初始化并查集
    3.按照先相等后不等的顺序,先维护所有的相等关系;最后在依次查看每一对不等关系是否出现矛盾

    坑点
    1.一开始的数组大小要开两倍
    2.离散化之后注意映射是从0开始还是从1开始
    3.并查集初始化有一个小技巧,初始化的数量直接是alls数组离散化去重之后的元素个数(下标从1开始映射)
    4.离散化的查询和合并操作都应该在映射的数上进行操作


     

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. const int N = 2000010;
    8. int n, m;
    9. int p[N];
    10. unordered_map<int, int> S;
    11. struct Query
    12. {
    13. int x, y, e;
    14. }query[N];
    15. int get(int x)
    16. {
    17. if(S.count(x) == 0) S[x] = ++ n;
    18. return S[x];
    19. }
    20. int find(int x)
    21. {
    22. if(p[x] != x) p[x] = find(p[x]);
    23. return p[x];
    24. }
    25. int main()
    26. {
    27. int T; cin >> T;
    28. while(T -- )
    29. {
    30. n = 0;
    31. cin >> m;
    32. S.clear();
    33. for(int i = 0; i < m; i ++ )
    34. {
    35. int x, y, e;
    36. scanf("%d%d%d", &x, &y, &e);
    37. query[i] = {get(x), get(y), e};
    38. }
    39. for(int i = 1; i <= n; i ++ ) p[i] = i;
    40. // 合并所有的相等条件
    41. for(int i = 0; i < m; i ++ )
    42. if(query[i].e == 1)
    43. {
    44. int pa = find(query[i].x), pb = find(query[i].y);
    45. p[pa] = p[pb];
    46. }
    47. // 检查所有不等条件
    48. bool has_conflict = false;
    49. for(int i = 0; i < m; i ++ )
    50. if(query[i].e == 0)
    51. {
    52. int pa = find(query[i].x), pb = find(query[i].y);
    53. if(pa == pb)
    54. {
    55. has_conflict = true;
    56. break;
    57. }
    58. }
    59. if(has_conflict) puts("NO");
    60. else puts("YES");
    61. }
    62. return 0;
    63. }

    AcWing 238. 银河英雄传说 

    输入样例:

    1. 4
    2. M 2 3
    3. C 1 2
    4. M 2 4
    5. C 4 2

    输出样例:

    1. -1
    2. 1

    ① 不问间隔多少战舰 -》 并查集

    ② 同时维护间隔多少战舰 -》 统一维护当前战舰到排头的距离

    ③ \left\{\begin{matrix}\left | d(x)-d(y) \right |,x !=y \\ 0,x=y \end{matrix}\right. 

    d[x] 表示 x 到 p[x] 的距离


    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 30010;
    7. int m;
    8. int p[N], sizes[N], d[N];
    9. int find(int x)
    10. {
    11. if(p[x] != x)
    12. {
    13. int root = find(p[x]);
    14. d[x] += d[p[x]];
    15. p[x] = root;
    16. }
    17. return p[x];
    18. }
    19. int main()
    20. {
    21. cin >> m;
    22. for(int i = 1; i < N; i ++ )
    23. {
    24. p[i] = i;
    25. sizes[i] = 1;
    26. }
    27. while(m -- )
    28. {
    29. char op[2];
    30. int a, b;
    31. scanf("%s%d%d", op, &a, &b);
    32. if(op[0] == 'M')
    33. {
    34. int pa = find(a), pb = find(b);
    35. if (pa != pb) { // 新加的,不在一个集合中才合并!!!
    36. d[pa] = sizes[pb]; // pa 到 pb 的距离为 pb 中节点的个数
    37. sizes[pb] += sizes[pa]; // 更新 pb 中节点的个数
    38. p[pa] = pb; // 将 pa 合并到 pb 中
    39. }
    40. }
    41. else
    42. {
    43. int pa = find(a), pb = find(b);
    44. if(pa != pb) puts("-1");
    45. else printf("%d\n", max(0, abs(d[a] - d[b]) - 1));
    46. }
    47. }
    48. return 0;
    49. }

    AcWing 239. 奇偶游戏 

    输入样例:

    1. 10
    2. 5
    3. 1 2 even
    4. 3 4 odd
    5. 5 6 even
    6. 1 6 even
    7. 7 10 odd

    输出样例:

    3
    
    来源:《算法竞赛进阶指南》, POJ1733 , kuangbin专题

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 20010;
    7. int n, m;
    8. int p[N], d[N];
    9. unordered_map<int, int> S;
    10. int get(int x)
    11. {
    12. if (S.count(x) == 0) S[x] = ++ n;
    13. return S[x];
    14. }
    15. int find(int x)
    16. {
    17. if (p[x] != x)
    18. {
    19. int root = find(p[x]);
    20. d[x] += d[p[x]];
    21. p[x] = root;
    22. }
    23. return p[x];
    24. }
    25. int main()
    26. {
    27. cin >> n >> m;
    28. n = 0;
    29. for (int i = 0; i < N; i ++ ) p[i] = i;
    30. int res = m;
    31. for (int i = 1; i <= m; i ++ )
    32. {
    33. int a, b;
    34. string type;
    35. cin >> a >> b >> type;
    36. a = get(a - 1), b = get(b);
    37. int t = 0;
    38. if (type == "odd") t = 1;
    39. int pa = find(a), pb = find(b);
    40. if (pa == pb)
    41. {
    42. if (((d[a] + d[b]) % 2 + 2) % 2 != t)
    43. {
    44. res = i - 1;
    45. break;
    46. }
    47. }
    48. else
    49. {
    50. p[pa] = pb;
    51. d[pa] = d[a] ^ d[b] ^ t;
    52. }
    53. }
    54. cout << res << endl;
    55. return 0;
    56. }

     

     

     

     

     

     

  • 相关阅读:
    场景应用:接口和抽象类有什么区别?你平时怎么用?
    基于TI Sitara系列AM5728工业开发板——FPGA视频开发案例分享
    10【DCL数据库控制语言】
    tictoc例子理解 16-18
    vxe-table 打包部署上线,校验样式失效
    A01、分布式文件系统
    【飞桨PaddleSpeech语音技术课程】— 声纹检索系统与实践
    腾讯算法实习面试总结
    vue3+vite配置eslint&prettier
    NFT交易系统平台开发 数字藏品平台开发解决方案:区块链+艺术品搭建 打造元宇宙市场数字化经济
  • 原文地址:https://blog.csdn.net/forever_bryant/article/details/126097535