##[$AcWing$ $239$ . 奇偶游戏](https://www.acwing.com/problem/content/241/) ### 一、题目描述 小 $A$ 和小 $B$ 在玩一个游戏。 首先,小 $A$ 写了一个由 $0$ 和 $1$ 组成的序列 $S$,长度为 $N$。 然后,小 $B$ 向小 $A$ 提出了 $M$ 个问题。 在每个问题中,小 $B$ 指定两个数 $l$ 和 $r$,小 $A$ 回答 $S[l∼r]$ 中有奇数个 $1$ 还是偶数个 $1$。 机智的小 $B$ 发现小 $A$ 有可能在撒谎。 例如,小 $A$ 曾经回答过 $S[1∼3]$ 中有奇数个 $1$,$S[4∼6]$ 中有偶数个 $1$,现在又回答 $S[1∼6]$ 中有偶数个 $1$,显然这是自相矛盾的。 请你帮助小 $B$ 检查这 $M$ 个答案,并指出在至少多少个回答之后可以确定小 $A$ 一定在撒谎。 即求出一个最小的 $k$,使得 $01$ 序列 $S$ 满足第 $1∼k$ 个回答,但不满足第 $1∼k+1$ 个回答。 **输入格式** 第一行包含一个整数 $N$,表示 $01$ 序列长度。 第二行包含一个整数 $M$,表示问题数量。 接下来 $M$ 行,每行包含一组问答:两个整数 $l$ 和 $r$,以及回答 $even$ 或 $odd$,用以描述 $S[l∼r]$ 中有偶数个 $1$ 还是奇数个 $1$。 **输出格式** 输出一个整数 $k$,表示 $01$ 序列满足第 $1∼k$ 个回答,但不满足第 $1∼k+1$ 个回答,如果 $01$ 序列满足所有回答,则输出问题总数量。 **数据范围** $N≤10^9,M≤5000$ **输入样例**: ```cpp {.line-numbers} 10 5 1 2 even 3 4 odd 5 6 even 1 6 even 7 10 odd ``` **输出样例**: ```cpp {.line-numbers} 3 ``` ### 二、题目解析 **前缀和** * 如果区间$[l, r]$有偶数个$1$,那么$s[r]$和$s[l-1]$的奇偶性一定相同,因为偶数-偶数=偶数 * 如果区间$[l, r]$有奇数个$1$,那么$s[r]$和$s[l-1]$的奇偶性一定不同,因为偶数-奇数=奇数,或者,奇数-奇数=偶数 这样一来,维护区间信息就变成维护俩端点的信息了。 往广义的来说,**并查集维护的是俩俩元素之间的信息**。(这个信息,可以是 **是否联通** ,也可以是 **奇偶性是否相同** ,还可以是 **两点距离** 等等) 对于这一题,并查集维护的是 **俩元素间的奇偶性关系**,$d[x]$表示$x$点与父亲的关系,$0$代表奇偶性相同,$1$代表奇偶性不同。那么显然每个点与根的奇偶关系就可以通过做路径上的边权做一遍异或即可。 ### 三、带权并查集+$STL$离散化 ```cpp {.line-numbers} #include using namespace std; const int N = 20010; int n, m; int p[N], d[N]; // 无序离散化 unordered_map S; int get(int x) { if (S[x] == 0) S[x] = ++n; // x映射为第n个数字 return S[x]; } // 带边权更新并查集模板 int find(int x) { if (x == p[x]) return x; int root = find(p[x]); d[x] += d[p[x]]; return p[x] = root; } int main() { scanf("%d %d", &n, &m); n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数 for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集 int res = m; for (int i = 1; i <= m; i++) { int a, b; // a~b之间1是奇数个还是偶数个 scanf("%d %d", &a, &b); char type[100]; // 字符数组 scanf("%s", type); // 类前缀和 a = get(a - 1), b = get(b); int t = 0; // 偶数个1 if (type[0] == 'o') t = 1; // 奇数个1 // 并查集 int pa = find(a), pb = find(b); if (pa == pb) { if (abs(d[a] - d[b]) % 2 != t) { res = i - 1; // 最后一条正确的序号 break; } } else { p[pa] = pb; d[pa] = abs(d[a] - d[b] - t) % 2; } } printf("%d\n", res); return 0; } ``` ### 四、带权并查集+静态数组+二分离散化 ```cpp {.line-numbers} #include using namespace std; const int N = 20010; // 结构体记录原始输入 struct Node { int x, y, e; } g[N]; int n, m; // 离散化静态数组+二分查找新位置 int b[N], bl; int get(int x) { return lower_bound(b, b + bl, x) - b; } // 带边权更新并查集模板 int p[N], d[N]; int find(int x) { if (x == p[x]) return x; int root = find(p[x]); d[x] += d[p[x]]; return p[x] = root; } int main() { scanf("%d %d", &n, &m); n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数 for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集 for (int i = 1; i <= m; i++) { int x, y; char t[100]; scanf("%d %d %s", &x, &y, t); g[i].x = x, g[i].y = y; if (t[0] == 'e') g[i].e = 0; else g[i].e = 1; // 记录下来 b[bl++] = x, b[bl++] = y; } // 离散化去重 sort(b, b + 2 * m); bl = unique(b, b + 2 * m) - b; int res = m; for (int i = 1; i <= m; i++) { int a = g[i].x, b = g[i].y, e = g[i].e; // 类前缀和 a = get(a - 1), b = get(b); int t = 0; // 偶数个1 if (e == 1) t = 1; // 奇数个1 // 并查集 int pa = find(a), pb = find(b); if (pa == pb) { if (abs(d[a] - d[b]) % 2 != t) { res = i - 1; // 最后一条正确的序号 break; } } else { p[pa] = pb; d[pa] = abs(d[a] - d[b] - t) % 2; } } printf("%d\n", res); return 0; } ``` ### 五、扩展域+$STL$离散化+并查集 ```cpp {.line-numbers} #include using namespace std; const int N = 40010, B = N / 2; // 简化版本的食物链 int n, m; // 无序离散化 unordered_map S; int get(int x) { if (S[x] == 0) S[x] = ++n; return S[x]; } // 并查集 int p[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { scanf("%d %d", &n, &m); n = 0; for (int i = 1; i < N; i++) p[i] = i; int res = m; for (int i = 1; i <= m; i++) { int a, b; scanf("%d %d", &a, &b); char t[100]; scanf("%s", t); a = get(a - 1), b = get(b); // 计算出新的在并查集中的号 if (t[0] == 'e') { // 偶数个1 if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同 res = i - 1; break; } // join两个奇偶相同的集合 p[find(a)] = find(b); p[find(a + B)] = find(b + B); } else { // 奇数个1 if (find(a) == find(b)) { res = i - 1; break; } // join两个奇偶不相同的集合 p[find(a + B)] = find(b); p[find(a)] = find(b + B); } } printf("%d\n", res); return 0; } ``` ### 六、静态数组离散化+二分+扩展域并查集 ```cpp {.line-numbers} #include using namespace std; const int N = 40010, B = N / 2; // 结构体记录原始输入 struct Node { int x, y, e; } g[N]; int n, m; // 离散化静态数组+二分查找新位置 int b[N], bl; int get(int x) { return lower_bound(b, b + bl, x) - b; } // 并查集 int p[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { scanf("%d %d", &n, &m); n = 0; for (int i = 1; i < N; i++) p[i] = i; for (int i = 1; i <= m; i++) { int x, y; char t[100]; scanf("%d %d %s", &x, &y, t); g[i].x = x, g[i].y = y; if (t[0] == 'e') g[i].e = 0; else g[i].e = 1; // 记录下来 b[bl++] = x, b[bl++] = y; } // 离散化去重 sort(b, b + 2 * m); bl = unique(b, b + 2 * m) - b; int res = m; for (int i = 1; i <= m; i++) { int a = g[i].x, b = g[i].y, e = g[i].e; a = get(a - 1), b = get(b); // 计算出新的在并查集中的号 if (e == 0) { // 偶数个1 if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同 res = i - 1; break; } // join两个奇偶相同的集合 p[find(a)] = find(b); p[find(a + B)] = find(b + B); } else { // 奇数个1 if (find(a) == find(b)) { res = i - 1; break; } // join两个奇偶不相同的集合 p[find(a + B)] = find(b); p[find(a)] = find(b + B); } } printf("%d\n", res); return 0; } ```