并查集 带权并查集 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;
}