[NOIP2023] 三值逻辑 题解
解
思考怎么去刻画这个赋值操作。
对每个变量\(i\) 维护一个 \(val_i\) 表示 \(i\) 的值(\(0\) 为 \(U\)、\(1\) 为 \(T\)、\(-1\) 为 \(F\)),同时对每个变量 \(i\) 维护一个 \(cs_i\) 表示 \(i\) 是否被确定为 \(U\)、\(T\)、\(F\) 三者之一。
考虑去遍历这 \(m\) 次操作。
如果为 TFU 这三种操作,那么这个变量此时就能够被确定值。
如果为 +- 这两种操作,考虑再维护两个数组,\(pre_i\) 表示变量 \(i\) 是由哪个变量赋值而来,\(op_i\) 表示 \(pre_i\) 赋值到变量 \(i\) 是否要取反。
那么赋值的代码就可以写出来了。
for ( int i = 1; i <= m; i ++) {
string s;
int u, v;
cin >> s >> u;
if (s[0] == 'T') cs[u] = 1, val[u] = 1;
if (s[0] == 'F') cs[u] = 1, val[u] = -1;
if (s[0] == 'U') cs[u] = 1, val[u] = 0;
if (s[0] == '+') {
cin >> v;
if (cs[v]) cs[u] = 1, val[u] = val[v];
else cs[u] = 0, pre[u] = pre[v], op[u] = op[v];
}
if (s[0] == '-') {
cin >> v;
if (cs[v]) cs[u] = 1, val[u] = -val[v];
else cs[u] = 0, pre[u] = pre[v], op[u] = -op[v];
}
}
可以很直观的看出来,维护 \(pre\) 的过程,就是在压缩操作。
然后考虑怎么统计答案,因为有取反,所以考虑扩展域并查集,\(i+n\) 就代表 \(i\) 取反后的结果。
现在已经得到了 \(i\) 与 \(pre_i\),如果 \(pre_i\) 赋值到 \(i\) 不取反,那么在并查集中合并 \((i,pre_i)\) 与 \((i+n,pre_i+n)\)。
如果要取反,那么在并查集中合并 \((i,pre_i+n)\) 与 \((i+n,pre_i)\)。
那么最后肯定会形成若干个连通块,如果一个连通块里有 \(U\),那么这个连通块全都应该是 \(U\)。
还有一种情况,如果 \(i\) 与 \(i+n\) 在同一个连通块内,这个连通块也应该全是 \(U\)。
找到全是 \(U\) 的连通块,把答案累加上连通块大小即可。
细节:每一个连通块只能被统计一次,所以在统计完这个连通块后,把它的大小清空。
代码
#include <bits/stdc++.h>
void Freopen() {
freopen("", "r", stdin);
freopen("", "w", stdout);
}
using namespace std;
const int N = 2e5 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;
int n, m;
int cs[N], op[N], val[N], pre[N];
int fa[N], siz[N];
int getf( int x) {
return x == fa[x] ? x : fa[x] = getf(fa[x]);
}
void merge( int u, int v) {
u = getf(u), v = getf(v);
if (u != v) fa[u] = v, siz[v] += siz[u];
}
void solve() {
cin >> n >> m;
for ( int i = 1; i <= n; i ++)
cs[i] = 0, op[i] = 1, pre[i] = i;
for ( int i = 1; i <= 2 * n; i ++)
fa[i] = i, siz[i] = (i <= n);
for ( int i = 1; i <= m; i ++) {
string s;
int u, v;
cin >> s >> u;
if (s[0] == 'T') cs[u] = 1, val[u] = 1;
if (s[0] == 'F') cs[u] = 1, val[u] = -1;
if (s[0] == 'U') cs[u] = 1, val[u] = 0;
if (s[0] == '+') {
cin >> v;
if (cs[v]) cs[u] = 1, val[u] = val[v];
else cs[u] = 0, pre[u] = pre[v], op[u] = op[v];
}
if (s[0] == '-') {
cin >> v;
if (cs[v]) cs[u] = 1, val[u] = -val[v];
else cs[u] = 0, pre[u] = pre[v], op[u] = -op[v];
}
}
for ( int i = 1; i <= n; i ++)
if (! cs[i]) {
int x = pre[i];
if (op[i] == 1) merge(i, x), merge(i + n, x + n);
else merge(i, x + n), merge(i + n, x);
}
int ans = 0;
for ( int i = 1; i <= n; i ++) {
if (cs[i] && val[i] == 0) {
int p = getf(i);
ans += siz[p], siz[p] = 0;
p = getf(i + n);
ans += siz[p], siz[p] = 0;
} else if (getf(i) == getf(i + n)) {
int p = getf(i);
ans += siz[p], siz[p] = 0;
}
}
cout << ans << '\n';
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int c, t;
cin >> c >> t;
while (t --) solve();
return 0;
}
总结
对于 01 取反的题目,可以考虑扩展域并查集。

浙公网安备 33010602011771号