题目地址:
https://www.acwing.com/problem/content/241/
小 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,以及回答even或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
考虑序列的前缀和数组 p p p,如果 S [ l : r ] S[l:r] S[l:r]中有奇数个 1 1 1,那么 p [ r ] p[r] p[r]和 p [ l − 1 ] p[l-1] p[l−1]奇偶性不同,否则奇偶性相同。我们可以通过小 A A A的回答将 N N N个前缀和按奇偶性分类,如果某个时刻小 A A A的回答使得某两个前缀和的奇偶性有矛盾,则可以得出他在说谎。如果没有矛盾,则可以将涉及的 S [ k i ] S[k_i] S[ki]列出,每次考虑 S [ k i ] S[k_i] S[ki]与 S [ k i + 1 ] S[k_{i+1}] S[ki+1]的奇偶性,如果不能判断,则 A [ k i + 1 : k i + 1 ] A[k_i+1:k_{i+1}] A[ki+1:ki+1]的值可以任意指定,否则可以随意指定,只要使得 ∑ A [ k i + 1 : k i + 1 ] \sum A[k_i+1:k_{i+1}] ∑A[ki+1:ki+1]符合 S [ k i ] S[k_i] S[ki]与 S [ k i + 1 ] S[k_{i+1}] S[ki+1]的奇偶性情形,这是一定可以做到的,所以如果奇偶性没有矛盾,不能得出小 A A A一定说谎的结论。所以我们只需判断奇偶性有没有矛盾就行了。
考虑开一个并查集,维护奇偶性关系能够被确定的前缀和(很显然”能确定奇偶性关系“这个关系是个等价关系),并对并查集加上边权,边权为
0
0
0代表边的两个端点奇偶性相同,边权为
1
1
1代表边的两个端点奇偶性不同,以
d
[
x
]
d[x]
d[x]数组存储
x
x
x与其父亲的奇偶性关系的情况。对于每次询问
l
,
r
l,r
l,r,我们就能确定
p
[
l
−
1
]
p[l-1]
p[l−1]和
p
[
r
]
p[r]
p[r]的奇偶性是否相同,接着查看一下
x
=
p
[
l
−
1
]
x=p[l-1]
x=p[l−1]和
y
=
p
[
r
]
y=p[r]
y=p[r]在各自集合里的树根:
1、如果树根相同,则说明之前小
A
A
A的回答是能够确定
x
x
x和
y
y
y的奇偶性关系的,路径压缩之后
d
[
x
]
d[x]
d[x]和
d
[
y
]
d[y]
d[y]就分别存了
x
x
x和
y
y
y各自相对于树根的奇偶性关系,如果
d
[
x
]
∧
d
[
y
]
=
0
d[x]\land d[y]=0
d[x]∧d[y]=0,则说明
x
x
x和
y
y
y的奇偶性相同,否则说明不同;开一个变量
t
t
t,定义其为如果当前小
A
A
A的回答是
x
x
x与
y
y
y奇偶性不同,则
t
=
1
t=1
t=1,否则
t
=
0
t=0
t=0,那么如果
d
[
x
]
∧
d
[
y
]
≠
t
d[x]\land d[y]\ne t
d[x]∧d[y]=t,则说明有矛盾,就能输出答案了;否则说明没有矛盾,继续看小
A
A
A的应答。
2、如果树根不同,则说明之前小
A
A
A的回答不能确定
x
x
x和
y
y
y的奇偶性关系,而当前小
A
A
A的回答可以确定关系,我们假设小
A
A
A说的为真,那么在路径压缩之后,
p
x
p_x
px和
p
y
p_y
py的奇偶性关系就可以由
d
[
x
]
d[x]
d[x]和
d
[
y
]
d[y]
d[y]确定了。不妨我们要把
x
x
x合并进
y
y
y(即
p
x
p_x
px指向
p
y
p_y
py),开一个变量
t
t
t,意思同上,那么有
d
[
x
]
∧
d
[
y
]
∧
d
[
p
x
]
=
t
d[x]\land d[y]\land d[p_x]=t
d[x]∧d[y]∧d[px]=t,所以有
d
[
p
x
]
=
d
[
x
]
∧
d
[
y
]
∧
t
d[p_x]=d[x]\land d[y]\land t
d[px]=d[x]∧d[y]∧t,即可以人为规定
d
[
p
x
]
=
d
[
x
]
∧
d
[
y
]
∧
t
d[p_x]=d[x]\land d[y]\land t
d[px]=d[x]∧d[y]∧t,就可以使得小
A
A
A说的话为真。
执行并查集里的查树根的操作需要维护 d d d的定义。并且,由于本题 M M M的范围远小于 N N N的上界,所以要离散化。代码如下:
#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;
const int N = 1e4 + 10;
int n, m;
int p[N], d[N];
unordered_map<int, int> mp;
int get(int x) {
if (!mp.count(x)) mp[x] = ++n;
return mp[x];
}
int find(int x) {
if (p[x] != x) {
int root = find(p[x]);
d[x] ^= d[p[x]];
p[x] = root;
}
return p[x];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < N; i++) p[i] = i;
n = 0;
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
char type[5];
scanf("%d%d%s", &a, &b, type);
a = get(a - 1), b = get(b);
// 如果奇偶性相同,则定义t = 0,否则t = 1
int t = 0;
if (type[0] == 'o') t = 1;
int pa = find(a), pb = find(b);
if (pa == pb) {
if ((d[a] ^ d[b]) != t) {
res = i - 1;
break;
}
} else {
p[pa] = pb;
d[pa] = d[a] ^ d[b] ^ t;
}
}
printf("%d\n", res);
return 0;
}
时间复杂度 O ( m log ∗ m ) O(m\log ^*m) O(mlog∗m),空间 O ( m ) O(m) O(m)。
也可以用所谓的”扩展域“并查集来做。对所有产生的 p [ k ] p[k] p[k],考虑两个布尔值,即 p [ k ] p[k] p[k]为奇和为偶。那么一共会产生不到 2 M 2M 2M个布尔值,在这些布尔值上定义等价关系,若 b 1 = b 2 b_1=b_2 b1=b2则它们等价(即若它们同真假则它们等价),每次小 A A A回答询问的时候,都会确定两对布尔值是同真假。例如,如果 x x x与 y y y同奇偶,那么 x x x为奇和 y y y为奇,这两个布尔值同真假;那么 x x x为偶和 y y y为偶,这两个布尔值也同真假;如果 x x x与 y y y不同奇偶,那么 x x x为奇和 y y y为偶,这两个布尔值同真假;并且 x x x为偶和 y y y为奇,这两个布尔值也同真假。如果某次小 A A A的回答会导致某两个布尔值原本同真假,但现在发生了矛盾,则说明小 A A A在说谎。代码如下:
#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;
const int N = (1e4 + 10) * 2, M = 1e4 + 10;
int n, m;
int p[N];
unordered_map<int, int> mp;
int get(int x) {
if (!mp.count(x)) mp[x] = ++n;
return mp[x];
}
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < N; i++) p[i] = i;
n = 0;
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
char type[5];
scanf("%d%d%s", &a, &b, type);
a = get(a - 1), b = get(b);
if (type[0] == 'e') {
// 如果a和b不同奇偶,但小A的回答说a与b同奇偶,
// 说明小A在说谎
if (find(a) == find(b + M)) {
res = i - 1;
break;
}
p[find(a)] = find(b);
p[find(a + M)] = find(b + M);
} else {
if (find(a) == find(b)) {
res = i - 1;
break;
}
p[find(a)] = find(b + M);
p[find(a + M)] = find(b);
}
}
printf("%d\n", res);
return 0;
}
时空复杂度一样。