并查集 带权并查集 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代表不同类。
而对于任意两点 , 若其在一个并查集内, 即具有同一个根:
那么 两点的关系就是 。比如当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;
}