并查集 带权并查集 T3 239. 奇偶游戏 - AcWing题库

题目

小 和小 在玩一个游戏。

首先,小 写了一个由 和 组成的序列 ,长度为 。

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

在每个问题中,小 指定两个数 和 ,小 回答 中有奇数个 还是偶数个 。

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

例如,小 曾经回答过 中有奇数个 , 中有偶数个 ,现在又回答 中有偶数个 ,显然这是自相矛盾的。

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

即求出一个最小的 ,使得 序列 满足第 个回答,但不满足第 个回答。

输入格式

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

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

接下来 行,每行包含一组问答:两个整数 和 ,以及回答 even 或 odd,用以描述 中有偶数个 还是奇数个 。

输出格式

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

数据范围

输入样例:

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

输出样例:

3

思路

既然是区间问题, 可以定义 为 的数中有多少个1, 且我们没必要真的统计有多少个, 只需要记录当前的奇偶属性, 故可以 。

进一步分析可得, 若 有奇数个1, 即 为奇数。根据奇偶性质, 同奇同偶为偶数, 不同为奇数的性质, 若差为奇数, 说明 的奇偶性不同。 反过来, 若有偶数个1, 则 和 奇偶性相同。

于是问题转化为, 给 m 个 值, 判断哪一步构成了自环。即当 和 奇偶性既相同又不同时出错。

对于这种两个点之间有多种关系, 可以用带权并查集来做, 典型的例子是食物链一题, 本题只有两种关系。

具体实现思想是, 定义 为 点相对于 根节点的关系, 该题中就是0, 1, 0代表同类, 1代表不同类。

而对于任意两点 , 若其在一个并查集内, 即具有同一个根:image.png 那么 两点的关系就是 。比如当x为偶数, 根为偶数, 而y为奇数, 对应的式子就是 0 + 1 = 1 不同类。

并查集能做的题目大多具有传递性, 且为无向边。

接着开始处理询问, 若给出的 为偶数, 即是同一类, 分两种情况:

  • 在同一集合内, , 此时若 为 0 则正确, 为 1 则冲突
  • 不在同一集合内, 将其合并即可, 合并时需要确定 连向 的边的权值, 这里为了保证是同一类, 即权值和为偶数, , 因此只需要连一条 的边即可。 若给出的 为奇数, 即是不同类, 也分两种情况:
  • 在同一集合内, 此时若 为 1 则正确, 为 0 则冲突
  • 不在一集合内, 合并时 连向 的边需要保证为不同类, 即权值和为奇数, 满足 , 故 为 。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <unordered_map>
using namespace std;
const int N = 5e4 + 10;
unordered_map<int,int> S;
int f[N], d[N], n;
 
int find(int x) {
    if(x != f[x]) 
    {
        int root = find(f[x]);
        d[x] = d[x] ^ d[f[x]];
        f[x] = root;
    }
    return f[x];
}
 
int get(int x)
{
    if(S.count(x) == 0) S[x] = ++n;
    return S[x];
}
 
int main()
{
    int m, res = 0;
    cin >> m;
    
    for(int i = 1; i < N ;i ++) f[i] = i, d[i] = 0;
    cin >> m;
    res = m;
    for(int i = 1; i <= m;i ++)
    {
        int a,b;
        string type;
        
        cin >> a >> b >> type;
        a = get(a - 1), b = get(b);
        
        int t = 0;
        if(type == "odd") t = 1;
        
        int pa = find(a), pb = find(b);
        if(pa == pb)
        {
            if((d[a] ^ d[b]) != t)
            {
                res = i - 1;
                break;
            }
        }
        else {
            d[pa] = d[a] ^ d[b] ^ t;
            f[pa] = pb;
        }
    }
    
    cout << res << endl;
    return 0;
}