• acwing 239. 奇偶游戏 并查集


    地址  https://www.acwing.com/problem/content/241/

    小A和小B在玩一个游戏。

    首先,小A写了一个由0和1组成的序列S,长度为N。

    然后,小B向小A提出了M个问题。

    在每个问题中,小B指定两个数 l 和 r,小A回答 S[l~r] 中有奇数个1还是偶数个1。

    机智的小B发现小A有可能在撒谎。

    例如,小A曾经回答过 S[1~3] 中有奇数个1, S[4~6] 中有偶数个1,现在又回答 S[1~6] 中有偶数个1,显然这是自相矛盾的。

    请你帮助小B检查这M个答案,并指出在至少多少个回答之后可以确定小A一定在撒谎。

    即求出一个最小的k,使得01序列S满足第1~k个回答,但不满足第1~k+1个回答。

    输入格式

    第一行包含一个整数N,表示01序列长度。

    第二行包含一个整数M,表示问题数量。

    接下来M行,每行包含一组问答:两个整数l和r,以及回答“even”或“odd”,用以描述S[l~r] 中有奇数个1还是偶数个1。

    输出格式

    输出一个整数k,表示01序列满足第1~k个回答,但不满足第1~k+1个回答,如果01序列满足所有回答,则输出问题总数量。

    数据范围
    N≤10^9,M≤10000
    输入样例:
    10
    5
    1 2 even
    3 4 odd
    5 6 even
    1 6 even
    7 10 odd
    输出样例:
    3

    解答1:

    带权并查集

    本题目有两个难点 

    1 数据范围过大  有 10^9 

    2 询问的内容如何转化到并查集

    问题1 的解决办法是 使用离散化 将不同的数据映射到1 2 3 4。。。,由于只有10000次 询问,每次询问提供两个数据 所以只要提供10000*2的数据范围即可

    问题2 的解决办法是 前缀和 如果提供 l和r的范围内1有奇数个还是偶数个 也就是计算 前缀和 sum[r] - sum[l-1]

    另由于有偶数减偶数 奇数减奇数 都是偶数   只有两者不同类型分别是奇数偶数中的一种 才会出现最后的差是奇数

    那么并查集其实也就是前缀和中每个元素的奇偶性

    增加带权数组 所有的前缀和元素都会合并到一个祖先中,d[x]带权数组会记录x元素是否与根是同样的奇偶性

    当得到新的询问时候 如果两个元素已经合并在同一个祖先下,那么就可以根据他们与祖先的异同得到他们的异同,再来判断他们与输入的异同是否一致 如果不一致就是发生矛盾,返回即可

    代码如下

     1 #include <iostream>
     2 #include <string>
     3 #include <unordered_map>
     4 
     5 using namespace std;
     6 
     7 const int MAX_M = 20010;
     8 
     9 int N, M;
    10 int n, m;
    11 
    12 int fa[MAX_M];
    13 int d[MAX_M];
    14 
    15 int idx = 0;
    16 
    17 unordered_map<int, int> S;
    18 
    19 
    20 //离散化
    21 int get(int x) {
    22     if (S.count(x) == 0) S[x] = ++idx;
    23     return S[x];
    24 }
    25 
    26 void init()
    27 {
    28     for (int i = 0; i < MAX_M; i++) {
    29         fa[i] = i;
    30     }
    31 }
    32 
    33 int find(int x) {
    34     if (fa[x] != x) {
    35         int root = find(fa[x]);
    36         d[x] += d[fa[x]]%2;
    37         fa[x] = root;
    38     }
    39 
    40     return fa[x];
    41 }
    42 
    43 
    44 int main()
    45 {
    46     
    47     cin >> n >> m;
    48     int res = m;
    49     init();
    50     for (int i = 1; i <= m; i++) {
    51         int a, b; string s;
    52         cin >> a >> b >> s;
    53         a = get(a - 1); b = get(b);
    54         int pa = find(a), pb = find(b);
    55 
    56         if (pa == pb) {
    57             //查看两者是否符合之前的信息
    58             if (s == "even")
    59             {
    60                 //两者奇偶性相同
    61                 if( (d[a] + d[b]) % 2 != 0){
    62                     //有矛盾
    63                     res = i - 1;
    64                     break;
    65                 }
    66             }
    67             else if (s == "odd") {
    68                 //两者奇偶性不同
    69                 if ((d[a] + d[b]) % 2 != 1) {
    70                     //有矛盾
    71                     res = i - 1;
    72                     break;
    73                 }
    74             }
    75             else {
    76                 cout << s << endl;
    77                 cout << "error" << endl;
    78                 break;
    79             }
    80         }
    81         else {
    82             //pa != pb
    83             //合并
    84             fa[pa] = pb;
    85             int add = 0;
    86             if (s == "odd") add = 1;
    87             d[pa] = (d[a] + d[b] + add)%2;
    88         }
    89 
    90 
    91     }
    92     cout << res << endl;
    93 
    94 
    95 
    96     return 0;
    97 }
    带权并查集

    解法2 并查集扩展域

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 #include <unordered_map>
     5 
     6 using namespace std;
     7 
     8 const int MAX_N = 20020*2; 
     9 const int Base = MAX_N / 2;
    10 
    11 unordered_map<int, int> S;
    12 int n, m;
    13 
    14 int p[MAX_N];
    15 
    16 int idx = 0;
    17 //离散化
    18 int get(int x) {
    19     if (S.count(x) == 0) S[x] = ++idx;
    20     return S[x];
    21 }
    22 
    23 int find(int x)
    24 {
    25     if (x != p[x]) p[x] = find(p[x]);
    26 
    27     return p[x];
    28 }
    29 
    30 int main()
    31 {
    32     cin >> n >> m; int res = m;
    33     for (int i = 0; i < MAX_N; i++) p[i] = i;
    34 
    35     for (int i = 1; i <= m; i++) {
    36         int a, b; string s;
    37         cin >> a >> b >> s;
    38         a = get(a - 1); b = get(b);
    39 
    40         if (s == "even") {
    41             //相同
    42             if (find(a + Base) == find(b)) {
    43                 res = i - 1;
    44                 break;
    45             }
    46             p[find(a)] = find(b);
    47             p[find(a + Base)] = p[find(b + Base)];
    48         }
    49         else {
    50             if (find(a) == find(b)) {
    51                 res = i - 1;
    52                 break;
    53             }
    54             p[find(a+Base)] = find(b);
    55             p[find(a )] = p[find(b + Base)];
    56 
    57         }
    58         
    59 
    60     }
    61     cout << res << endl;
    62 
    63     return 0;
    64 }
    扩展域
    作 者: itdef
    欢迎转帖 请保持文本完整并注明出处
    技术博客 http://www.cnblogs.com/itdef/
    B站算法视频题解
    https://space.bilibili.com/18508846
    qq 151435887
    gitee https://gitee.com/def/
    欢迎c c++ 算法爱好者 windows驱动爱好者 服务器程序员沟通交流
    如果觉得不错,欢迎点赞,你的鼓励就是我的动力
    阿里打赏 微信打赏
  • 相关阅读:
    19 C#循环语句的跳过和中断 continue和break
    18 C#中的循环执行 for循环
    一种绝对提高开发水平的方法(推荐英语)
    大数据知识普及
    全链路压测压测报告
    QuickSearch快排
    二分查找
    算法
    基于领域驱动设计的业务中台架构设计
    【科普】Scrum——从橄榄球争球到敏捷开发
  • 原文地址:https://www.cnblogs.com/itdef/p/12115453.html
Copyright © 2020-2023  润新知