P9869 [NOIP2023] 三值逻辑题解

题目链接

这里使用扩展域并查集实现。

既然来看题解了,相信你已经读懂题了,这里不再赘述题意,直接进入主题。

没读懂可以去看其他题解

思路:

为何用并查集

考虑什么情况下一个变量的最终值为 \(U\)?当且仅当一个变量 \(x=-x\)\(x\)\(-x\) 中有一个等于 \(U\) 那么这个变量就是 \(U\)

关于维护 \(x\)\(-x\) 以及与 \(U\) 之间的关系,可以想到用扩展域并查集。正负对应 \([1,n]\)\([n+1,2n]\)\(T\)\(F\)\(U\) 分别对应 \(2n+1\)\(2n+2\)\(2n+3\)

维护赋值操作

此题的特殊点在于初始值都不知道,无法直接进行赋值操作。所以考虑如何维护。

我们记每个变量的最终值为 \(ed_i\)

一个错误的思路就是用 \(ed_x\) 表示当前 \(x\) 赋到的值的下标 \(y\)。这样子做的问题在于 \(ed_y\) 在不停改变,如果 \(ed_y\) 又发生了改变,那么 \(ed_x\) 相应的也会被改变。

但是,如果每个变量的初始值已知,那么就可以直接进行赋值,且不会受到后续变量改变的干扰。

所以,我们假设初始值已知(令初始时 \(ed_i=i\)),然后直接进行赋值操作。最终可以得到 \(ed_i\) 等于某个值 \(x\)。由于变量的初始值等于它的最终值,所以 \(i\)\(x\) 属于同一集合,\(-i\)\(-x\) 属于同一集合,这样就能用并查集维护了。

(记得更新完后还要更新相应负值

统计答案

\(x=-x\)\(x\)\(-x\) 中有一个等于 \(U\) 的与 \(U\) 合并,最后判断哪些 \(i\)\(U\) 在同一集合即可。

实现:

赋值操作

    for(int i=1;i<=2*n;i++) ed[i]=i;
    for(int i=1;i<=m;i++){
        int x=op[i].x;
        int y=op[i].y;
        if(op[i].op=='+') ed[x]=ed[y];
        else if(op[i].op=='-') ed[x]=-ed[y];
        else ed[x]=y;
    }
    //更新对应负值
    for(int i=1;i<=n;i++){
        if(ed[i]==T) ed[i+n]=F;
        else if(ed[i]==-T) ed[i]=F,ed[i+n]=T;
        else if(ed[i]==F) ed[i+n]=T;
        else if(ed[i]==-F) ed[i]=T,ed[i+n]=F;
        else if(ed[i]==U||ed[i]==-U) ed[i]=ed[i+n]=U;
        else if(ed[i]<0){
            int tmp=ed[i];
            ed[i]=-ed[i]+n;
            ed[i+n]=-tmp;
        }
        else if(ed[i]>0) ed[i+n]=ed[i]+n;
    }

并查集维护

    for(int i=1;i<=U;i++) fa[i]=i;
    for(int i=1;i<=n;i++){
        add(i,ed[i]);//i=edi
        add(i+n,ed[i+n]);//!i=!edi
    }
    for(int i=1;i<=n;i++){
        if(getf(i)==getf(i+n)){
            add(i,U);
            add(i+n,U);
        }
    }

完整代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define T (2*n+1)
#define F (2*n+2)
#define U (2*n+3)
using namespace std;
const int N=2e5+20;
int n,m;
int fa[N],ed[N];
struct optition{
    char op;
    int x,y;
}op[N];
int getf(int x){
    if(x!=fa[x]) fa[x]=getf(fa[x]);
    return fa[x];
}
void add(int x,int y){
    int fx=getf(x);
    int fy=getf(y);
    if(fy!=fx){
        fa[fy]=fx;
    }
}
void solve(){
    for(int i=1;i<=2*n;i++) ed[i]=i;
    for(int i=1;i<=m;i++){
        int x=op[i].x;
        int y=op[i].y;
        if(op[i].op=='+') ed[x]=ed[y];
        else if(op[i].op=='-') ed[x]=-ed[y];
        else ed[x]=y;
    }
    //更新对应负值
    for(int i=1;i<=n;i++){
        if(ed[i]==T) ed[i+n]=F;
        else if(ed[i]==-T) ed[i]=F,ed[i+n]=T;
        else if(ed[i]==F) ed[i+n]=T;
        else if(ed[i]==-F) ed[i]=T,ed[i+n]=F;
        else if(ed[i]==U||ed[i]==-U) ed[i]=ed[i+n]=U;
        else if(ed[i]<0){
            int tmp=ed[i];
            ed[i]=-ed[i]+n;
            ed[i+n]=-tmp;
        }
        else if(ed[i]>0) ed[i+n]=ed[i]+n;
    }
    for(int i=1;i<=U;i++) fa[i]=i;
    for(int i=1;i<=n;i++){
        add(i,ed[i]);//i=edi
        add(i+n,ed[i+n]);//!i=!edi
    }
    for(int i=1;i<=n;i++){
        if(getf(i)==getf(i+n)){
            add(i,U);
            add(i+n,U);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(getf(i)==getf(U)) ans++;
    }
    printf("%d\n",ans);
}
int main(){
    #ifdef debug
    freopen("tribool2.in","r",stdin);
    freopen("tribool.out","w",stdout);
    #endif
    int c,t;
    scanf("%d%d",&c,&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            getchar();
            scanf("%c%d",&op[i].op,&op[i].x);
            if(op[i].op=='T') op[i].y=T;
            else if(op[i].op=='F') op[i].y=F;
            else if(op[i].op=='U') op[i].y=U;
            else scanf("%d",&op[i].y);
        }
        solve();
    }
    return 0;
}
posted @ 2026-01-27 21:14  Kx_Triumphs  阅读(10)  评论(0)    收藏  举报