[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 取反的题目,可以考虑扩展域并查集。

posted @ 2025-08-15 10:05  咚咚的锵  阅读(18)  评论(0)    收藏  举报