题解:P9869 [NOIP 2023] 三值逻辑

Firsry AC/WA/RE/TLE

扩展域并查集题解

如果我考场上看见这个题目,肯定不会觉得和并查集有半点关系

约束系统

我们的目标是找到一个初始值方案 ,使得对于每个 ,都有

我们分析依赖关系,推导出 ,其表达式总是以下三种形式之一:

代入 ,得到了关于初始值的约束方程:

这个推导是具有充分必要性的,也就是说,任何一个合法的初始值方案,必须满足这个由所有 条语句推导出的最终约束系统,且任何一个满足这个约束系统的赋值方案,也必然是一个合法的解。

所以,原问题就被转化成了“找到一个初始值方案使满足这个约束系统”。也就是说,只要在满足约束系统的条件下进行关系推导,得到的解就也是合法的,我们期望得到的,就是最小化 的那一个。

求最优解

我们用并查集来分析这个约束系统。这个分析过程会把所有变量划分成若干个不相交的连通块。会有很多的操作,让我们从结果开始分析。

什么时候是

  1. findAnc(x) == findAnc(x + n),我和我的非在一个逻辑值中,那肯定是
  2. anc[x] = U,就是在 这个集合当中,显然是

所以我们要维护的就是 anc 关系,并查集是可以的。

但是注意,不能 merge,因为赋值不会影响到祖先,只会影响到自己 。那有的同学就要问了:

欸?这个路径压缩是有滞后性的啊,你这么改会有问题啊?

现在我们就来思考,为什么这么做是满足约束要求的。由于我们的约束关系仅仅关于初始值,中途能够合并的点的初始值都是一致的。所以我们可以在过程中绕一圈,T -> F -> T 这样最终回到初始值,就仍然是合法的。这说明,我们让 的子树(更新滞后仍然依赖于 )和 一起走一遭在结果上看是没有问题的。

代码实现

有几个小细节:

初始化正常流程,如果说最后有一些联通块并没有和 中任意一个相连,说明他们可以随便赋成某个值,在最小化 的过程中是没有贡献的,不考虑即可;

对于 操作, 时注意用 swap

findAnc 可能会有环的情况,处理如下:

  • 如果是 ,那就直接 return 对应值即可;
  • 我们记录 vis,表示当前 dfs 过程中路径上的值:
    • vis[x + n] == true,说明 x, x + n 在同一联通块内,是
    • vis[x] = true,说明走回来了,是一个没有确定的祖先的独立于 的部分,返回一个非 值即可, 都行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include<bits/stdc++.h>

using namespace std;

const int MAXN = 2e5 + 7;

int c, t;

int n, m;
int a, b;
string opt;

int T, F, U;
int anc[MAXN];

bitset<MAXN> vis;

inline void ancInit(int siz) {
for (int i = 1; i <= siz; ++i)
anc[i] = i;
return;
}
inline int match(int x) {
if(x == T || x == U || x == F)
return x;
return (x <= n) ? x + n : x - n;
}
inline int findAnc(int cur) {
if (cur == F || cur == T || cur == U)
return cur;
if (vis[match(cur)])
return U;
if (vis[cur])
return T;
if (cur != anc[cur]) {
vis[cur] = true;
anc[cur] = findAnc(anc[cur]);
vis[cur] = false;
}
return anc[cur];
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

cin >> c >> t;
while (t--) {
int ans = 0;
cin >> n >> m;
T = 2 * n + 1;
F = 2 * n + 2;
U = 2 * n + 3;
ancInit(2 * n + 3);
while (m--) {
cin >> opt >> a;

if (opt == "T")
anc[a] = T, anc[a + n] = F;
else if (opt == "F")
anc[a] = F, anc[a + n] = T;
else if (opt == "U")
anc[a] = anc[a + n] = U;

else {
cin >> b;
if (opt == "+") {
anc[a] = anc[b];
anc[a + n] = anc[b + n];
} else {
if (a == b)
swap(anc[a], anc[a + n]);
else {
anc[a + n] = anc[b];
anc[a] = anc[b + n];
}
}
}
}

for (int i = 1; i <= n; ++i)
if (findAnc(i) == U)
ans++;
cout << ans << '\n';
}
return 0;
}
  • Title: 题解:P9869 [NOIP 2023] 三值逻辑
  • Author: Firsry
  • Created at : 2025-08-09 22:20:42
  • Updated at : 2025-08-10 09:28:49
  • Link: https://firsryfan.github.io/2025/08/09/题解:P9869-NOIP-2023-三值逻辑/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
题解:P9869 [NOIP 2023] 三值逻辑