• Parity Game——种类并查集、权值并查集、离散化


    题目描述

    思路

    怎么得到这个序列中每一段的关系?

    • 我们可以把这个只包含0和1的序列看作一个数组,0表示当前位置为0,1表示当前位置为1,利用前缀和的性质可以知道某一段中所包含的1的数量
    • sum1 = a[r] - a[l-1]
    1. 如果sum1为偶数,那么a[r] 和 a[l-1]的奇偶性相同
    2. 如果sum1为奇数,那么a[r] 和 a[l-1]的奇偶性不同
    • 找到它们之间的关系,我们就可以使用并查集来存储他们

    为什么要离散化?

    • 序列的长度小于等于1e9,然而序列中下标出现的次数最多为1e4,所以使用离散化

    种类并查集

    • 序列中某一段有两种关系,奇数个1、偶数个1
    • 我们定义两个扩展域,1~n表示偶数,n + 1 ~ 2 * n表示奇数
    • 每次知道一个关系之后,将偶数区域和奇数区域都进行处理,像枚举一样,不漏掉每一种情况
    • 并查集中存储的是区域与区域之间的关系,如果有一个关系成立,那么这个集合中其它的关系都成立

    代码实现

    1. #include <iostream>
    2. #include <algorithm>
    3. #include <vector>
    4. using namespace std;
    5. const int N = 1e4 + 10;
    6. int fa[2 * N]; // 两个扩展域:1~N表示偶数关系,N+1~2N表示奇数关系
    7. int n, m;
    8. vector<int> ans; // 离散化数组
    9. struct node // 结构体,存储每一段区间的左右端点
    10. {
    11. int x, y;
    12. string op;
    13. }a[N];
    14. int get(int u) // 二分查找,将原数据离散化成下标
    15. {
    16. int l = 0, r = ans.size();
    17. while(l + 1 != r)
    18. {
    19. int mid = l + r >> 1;
    20. if(ans[mid] < u) l = mid;
    21. else r = mid;
    22. }
    23. return r;
    24. }
    25. int find(int u) // 返回当前元素的祖宗元素,在哪个集合中
    26. {
    27. if(fa[u] != u) fa[u] = find(fa[u]);
    28. return fa[u];
    29. }
    30. void merge(int x, int y) // 将x合并到y所在的集合中
    31. {
    32. fa[find(x)] = find(y);
    33. }
    34. int main()
    35. {
    36. cin >> n >> m;
    37. for(int i = 1; i <= m; i++)
    38. {
    39. cin >> a[i].x >> a[i].y >> a[i].op;
    40. a[i].x--; // 因为利用了前缀和的性质,所以要将左端点-1,满足sum = a[r] - a[l-1]
    41. ans.push_back(a[i].x);
    42. ans.push_back(a[i].y);
    43. }
    44. sort(ans.begin(), ans.end()); // 将数据进行排序
    45. ans.erase(unique(ans.begin(), ans.end()), ans.end()); // 进行去重
    46. ans.insert(ans.begin(), 0); // 将离散化之后的下标从1开始
    47. for(int i = 1;i <= m; i++) // 找到每个区间左右端点离散化之后的数据,使用新数据
    48. {
    49. a[i].x = get(a[i].x);
    50. a[i].y = get(a[i].y);
    51. }
    52. for(int i = 0;i < 2 * N; i++) fa[i] = i; // 初始化集合,每一个元素在不同的集合中
    53. n = ans.size(); // 每个扩展域的范围
    54. for(int i = 1; i <= m; i++) // 开始遍历每一个回答,判断左右端点集合中的关系
    55. {
    56. int x = a[i].x, y = a[i].y;
    57. string op = a[i].op;
    58. if(op == "even") // 有偶数个1
    59. {
    60. // 只需要判断一种情况就可,因为他们的关系是对称的
    61. if(find(x) == find(y + n)) // x为偶数的集合中,存在y为奇数的关系,矛盾
    62. {
    63. cout << i - 1 << endl;
    64. return 0;
    65. }
    66. // x与y的奇偶性相同
    67. merge(x, y); // 将x为偶数,y为偶数放在一个集合中
    68. merge(x + n, y + n); // 将x为奇数,y为奇数放在一个集合中
    69. }
    70. else // 有奇数个1,那么x和y的奇偶性不同
    71. {
    72. if(find(x) == find(y)) // x为偶数的集合中,存在y为偶数的关系,矛盾
    73. {
    74. cout << i - 1 << endl;
    75. return 0;
    76. }
    77. merge(x, y + n); // 将x为偶数,y为奇数放在一个集合中
    78. merge(x + n, y); // 将x为奇数,y为偶数放在一个集合中
    79. }
    80. }
    81. cout << m << endl;
    82. return 0;
    83. }

    权值并查集

    • 每一个区间的奇偶性有两种情况,奇数或者偶数
    • 我们可以维护集合中每个区间的关系,这个关系可以用集合中的子区间到祖宗区间的距离来表示,因为有两种情况,所以当距离d[i] % 2 == 0时,当前区间的奇偶性和祖宗元素的奇偶性相同,d[i] % 2 ==1时,当前区间的奇偶性和祖宗元素的奇偶性不同
    • 每一个集合中的区间相对于祖宗区间的奇偶性是已知的,所以集合中所有区间的奇偶性关系都是已知的

    代码实现

    1. #include <iostream>
    2. #include <vector>
    3. #include <algorithm>
    4. using namespace std;
    5. const int N = 1e5 + 10;
    6. int fa[N]; // 初始化每一个集合都是独立的
    7. int d[N]; // 集合中子区间到祖宗区间的距离,可以判断奇偶关系
    8. vector<int> ans; // 离散化数组
    9. int n, m;
    10. struct node // 存储每一个回答
    11. {
    12. int x, y;
    13. string op;
    14. }a[N];
    15. int get(int u) // 二分查找进行离散化
    16. {
    17. int l = 0, r = ans.size();
    18. while(l + 1 != r)
    19. {
    20. int mid = l + r >> 1;
    21. if(ans[mid] < u) l = mid;
    22. else r = mid;
    23. }
    24. return r;
    25. }
    26. int find(int u) // 找到当前区间的祖宗区间,并进行路径压缩和更新到祖宗区间的距离
    27. {
    28. if(fa[u] != u)
    29. {
    30. int t = find(fa[u]);
    31. d[u] += d[fa[u]];
    32. fa[u] = t;
    33. }
    34. return fa[u];
    35. }
    36. int main()
    37. {
    38. cin >> n >> m;
    39. for(int i = 1;i <= m; i++)
    40. {
    41. cin >> a[i].x >> a[i].y >> a[i].op;
    42. a[i].x--;
    43. ans.push_back(a[i].x);
    44. ans.push_back(a[i].y);
    45. }
    46. sort(ans.begin(), ans.end());
    47. ans.erase(unique(ans.begin(), ans.end()), ans.end());
    48. ans.insert(ans.begin(), 0);
    49. for(int i = 1; i <= m; i++)
    50. {
    51. a[i].x = get(a[i].x);
    52. a[i].y = get(a[i].y);
    53. }
    54. for(int i = 1; i <= ans.size(); i++) fa[i] = i;
    55. for(int i = 1;i <= m; i++)
    56. {
    57. int fx = find(a[i].x), fy = find(a[i].y);
    58. string op = a[i].op;
    59. if(op == "even")
    60. {
    61. if(fx != fy)
    62. {
    63. d[fx] = d[a[i].x] ^ d[a[i].y];
    64. fa[fx] = fy;
    65. }
    66. else
    67. {
    68. if((d[a[i].x] + d[a[i].y]) % 2 != 0)
    69. {
    70. cout << i - 1 << endl;
    71. return 0;
    72. }
    73. }
    74. }
    75. else
    76. {
    77. if(fx != fy)
    78. {
    79. d[fx] = d[a[i].x] ^ d[a[i].y] ^ 1;
    80. fa[fx] = fy;
    81. }
    82. else
    83. {
    84. if((d[a[i].x] + d[a[i].y]) % 2 == 0)
    85. {
    86. cout << i - 1 << endl;
    87. return 0;
    88. }
    89. }
    90. }
    91. }
    92. cout << m << endl;
    93. return 0;
    94. }
  • 相关阅读:
    Docker-安装(Linux,Windows)
    树莓派(十二)树莓派驱动开发入门:从读懂框架到自己写驱动(下)
    TDengine 官网换了新“皮肤”,来看看这个风格是不是你的菜
    [思维]Color the Picture Codeforces1711C
    JVM学习笔记(四)类加载与字节码技术
    typescript ts基础知识之tsconfig.json配置选项
    全新升级的AOP框架Dora.Interception[汇总,共6篇]
    java spring cloud 工程企业管理软件-综合型项目管理软件-工程系统源码
    QT-QPainter
    油溶性硒化镉量子点,CdSe量子点,波长480nm-640 nm
  • 原文地址:https://blog.csdn.net/m0_73197206/article/details/134496214