AcWing 239 . 奇偶游戏
. 奇偶游戏
一、题目描述
小 和小 在玩一个游戏。
首先,小 写了一个由 和 组成的序列 ,长度为 。
然后,小 向小 提出了 个问题。
在每个问题中,小 指定两个数 和 ,小 回答 中有奇数个 还是偶数个 。
机智的小 发现小 有可能在撒谎。
例如,小 曾经回答过 中有奇数个 , 中有偶数个 ,现在又回答 中有偶数个 ,显然这是自相矛盾的。
请你帮助小 检查这 个答案,并指出在至少多少个回答之后可以确定小 一定在撒谎。
即求出一个最小的 ,使得 序列 满足第 个回答,但不满足第 个回答。
输入格式
第一行包含一个整数 ,表示 序列长度。
第二行包含一个整数 ,表示问题数量。
接下来 行,每行包含一组问答:两个整数 和 ,以及回答 或 ,用以描述 中有偶数个 还是奇数个 。
输出格式
输出一个整数 ,表示 序列满足第 个回答,但不满足第 个回答,如果 序列满足所有回答,则输出问题总数量。
数据范围
输入样例:
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
输出样例:
3
二、题目解析
前缀和
- 如果区间有偶数个,那么和的奇偶性一定相同,因为偶数-偶数=偶数
- 如果区间有奇数个,那么和的奇偶性一定不同,因为偶数-奇数=奇数,或者,奇数-奇数=偶数
这样一来,维护区间信息就变成维护俩端点的信息了。
往广义的来说,并查集维护的是俩俩元素之间的信息。(这个信息,可以是 是否联通 ,也可以是 奇偶性是否相同 ,还可以是 两点距离 等等)
对于这一题,并查集维护的是 俩元素间的奇偶性关系,表示点与父亲的关系,代表奇偶性相同,代表奇偶性不同。那么显然每个点与根的奇偶关系就可以通过做路径上的边权做一遍异或即可。
三、带权并查集+离散化
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
int n, m;
int p[N], d[N];
// 无序离散化
unordered_map<int, int> 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;
}
四、带权并查集+静态数组+二分离散化
#include <bits/stdc++.h>
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;
}
五、扩展域+离散化+并查集
#include <bits/stdc++.h>
using namespace std;
const int N = 40010, B = N / 2;
// 简化版本的食物链
int n, m;
// 无序离散化
unordered_map<int, int> 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;
}
六、静态数组离散化+二分+扩展域并查集
#include <bits/stdc++.h>
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2016-04-12 Python学习
2013-04-12 15、单机运行环境搭建之 --Centos6.4下对mysql进行压力测试
2013-04-12 14、单机运行环境搭建之 --Centos6.4下使用Denyhosts禁止针对linux sshd的暴力破解
2013-04-12 13、单机运行环境搭建之 --Centos6.4下iptables保护主机安全