题目链接
AcWing 239. 奇偶游戏
mid
题目描述
小 A A A 和小 B B B 在玩一个游戏。
首先,小 A A A 写了一个由 0 0 0 和 1 1 1 组成的序列 S S S,长度为 N N N。
然后,小 B B B 向小 A A A 提出了 M M M 个问题。
在每个问题中,小 B B B 指定两个数 l l l 和 r r r,小 A A A 回答 S [ l ∼ r ] S[l∼r] S[l∼r] 中有奇数个 1 1 1 还是偶数个 1 1 1。
机智的小 B B B 发现小 A A A 有可能在撒谎。
例如,小 A A A 曾经回答过 S [ 1 ∼ 3 ] S[1∼3] S[1∼3] 中有奇数个 1 1 1, S [ 4 ∼ 6 ] S[4∼6] S[4∼6] 中有偶数个 1 1 1,现在又回答 S [ 1 ∼ 6 ] S[1∼6] S[1∼6] 中有偶数个 1 1 1,显然这是自相矛盾的。
请你帮助小 B B B 检查这 M M M 个答案,并指出在至少多少个回答之后可以确定小 A A A 一定在撒谎。
即求出一个最小的 k k k,使得 01 01 01 序列 S S S 满足第 1 ∼ k 1∼k 1∼k个回答,但不满足第 1 ∼ k + 1 1∼k+1 1∼k+1 个回答。
输入格式
第一行包含一个整数 N N N,表示 01 01 01 序列长度。
第二行包含一个整数 M M M,表示问题数量。
接下来 M M M 行,每行包含一组问答:两个整数 l l l 和 r r r,以及回答 e v e n even even 或 o d d odd odd,用以描述 S [ l ∼ r ] S[l∼r] S[l∼r] 中有偶数个 1 1 1 还是奇数个 1 1 1。
输出格式
输出一个整数 k k k,表示 01 01 01 序列满足第 1 ∼ k 1∼k 1∼k 个回答,但不满足第 1 ∼ k + 1 1∼k+1 1∼k+1 个回答,如果 01 01 01 序列满足所有回答,则输出问题总数量。
数据范围
N ≤ 1 0 9 , M ≤ 5000 N≤10^9,M≤5000 N≤109,M≤5000
输入样例:
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
输出样例:
3
解法:带权值的并查集
如果我们使用 s u m sum sum 来表示序列 S S S 的前缀和的话。
- S [ l : r ] S[l :r] S[l:r]有偶数个 1 1 1,说明 s u m [ l − 1 ] sum[l - 1] sum[l−1] 和 s u m [ r ] sum[r] sum[r] 奇偶性相同;
- S [ l : r ] S[l :r] S[l:r]有奇数个 1 1 1,说明 s u m [ l − 1 ] sum[l - 1] sum[l−1] 和 s u m [ r ] sum[r] sum[r] 奇偶性不同;
传递关系:
- 如果 a a a 和 b b b 奇偶性相同, b b b 和 c c c 奇偶性相同,那么 a a a 和 c c c 奇偶性也相同;
- 如果 a a a 和 b b b 奇偶性相同, b b b 和 c c c 奇偶性不同,那么 a a a 和 c c c 奇偶性也不同;
- 如果 a a a 和 b b b 奇偶性不同, b b b 和 c c c 奇偶性不同,那么 a a a 和 c c c 奇偶性也相同;
因为
N
N
N 很大,而
M
M
M 比较小,所以我们还是使用离散化的技巧,将输入的数映射到 最大为
2
M
2M
2M 的数组中。在实现上,我们可以使用 map
来实现。
我们定义 d [ x ] d[x] d[x] 为 x x x 节点到它的父结点 p [ x ] p[x] p[x] 的边权。
- 如果 d [ x ] = 0 d[x] = 0 d[x]=0,表示节点 x x x 与它的父节点 p [ x ] p[x] p[x] 奇偶性相同;
- 如果 d [ x ] = 1 d[x] = 1 d[x]=1,表示节点 x x x 与它的父节点 p [ x ] p[x] p[x] 奇偶性不同;
路径压缩:
在路径压缩的时候,对于 节点 x x x 到其根节点路径上的所有边权值做 异或运算,就可以得到 节点 x x x 与其 根节点的奇偶性关系。
对于 x x x 和 y y y ,我们先检查它们是否在一个集合中,即 f i n d ( x ) = f i n d ( y ) find(x) = find(y) find(x)=find(y)。
- 如果在同一个集合中。如果 d [ x ] x o r d [ y ] ≠ t y p e d[x]\ xor\ d[y] \neq type d[x] xor d[y]=type,说明这个回答有矛盾,则在这个问题之后就能够确定小 A A A 撒谎了。
- 如果不在同一个集合之中,就需要先合并两个集合。
我们设 p , q p , q p,q 分别是 x , y x,y x,y 的祖宗节点。
我们已知 d [ x ] d[x] d[x] 和 d [ y ] d[y] d[y] 分别表示 路径 x ∼ p x \sim p x∼p 和 y ∼ p y \sim p y∼p 之间的所有边权 异或和。
p ∼ q p \sim q p∼q 这一段路径的异或和 d [ p ] d[p] d[p] 是我们要求的 。
由于 x ∼ y x \sim y x∼y 的路径是由 x ∼ p , p ∼ q , q ∼ y x \sim p , p \sim q , q \sim y x∼p,p∼q,q∼y 组成的, x x x 和 y y y 的奇偶性 t y p e type type 我们是已知的,因此 t y p e = d [ x ] x o r d [ y ] x o r d [ p ] type = d[x] \ xor \ d[y] \ xor \ d[p] type=d[x] xor d[y] xor d[p]。
所以 d [ p ] = d [ x ] x o r d [ y ] x o r t y p e d[p] = d[x] \ xor \ d[y] \ xor \ type d[p]=d[x] xor d[y] xor type。
时间复杂度: O ( n ) O(n) O(n)
C++代码:
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int N = 1e4+10;
struct{
int l,r,type;
}query[N];
int n,m;
int p[N] , d[N] , idx = 0;
map<int,int> mp;
//输入离散化
void read_discrete(){
cin>>n>>m;
char str[5];
for(int i = 1;i <= m;i++){
scanf("%d%d%s",&query[i].l,&query[i].r,str);
query[i].type = (str[0] == 'o' ? 1 : 0);
int l = query[i].l - 1 , r = query[i].r;
if(mp.find(l) == mp.end()) mp[l] = ++idx;
if(mp.find(r) == mp.end()) mp[r] = ++idx;
}
n = idx;
}
//路径压缩找根节点
int find(int x){
if(x == p[x]) return p[x];
int root = find(p[x]);
d[x] ^= d[p[x]];
return p[x] = root;
}
int main(){
read_discrete();
//cout<<n<<'\n';
for(int i = 1;i <= n;i++) p[i] = i;
for(int i = 1;i <= m;i++){
int l = query[i].l - 1 , r = query[i].r;
int a = mp[l] , b = mp[r];
int x = find(a) , y = find(b);
if(x == y){
//i轮发生矛盾,i - 1 轮及之前的都是没问题的
if((d[a] ^ d[b]) != query[i].type){
cout<<i - 1<<'\n';
return 0;
}
}
else{
p[x] = y;
d[x] = d[a] ^ d[b] ^ query[i].type;
}
}
//没有矛盾
cout<<m<<'\n';
return 0;
}