[NOIP2023] 三值逻辑 题解

[NOIP2023] 三值逻辑 题解


知识点

(扩展域)并查集,缩点,二分图,2-SAT。


题意简述

\(n\) 个逻辑变量 \(\{ x_i \}_1^n\),每逻辑变量值有三种可能:\(T\)\(F\)\(U\),分别表示 truefalseunknown

现在有一种运算符逻辑非运算 \(\lnot\),对于这三个值,运算结果分别为:\(\lnot T = F\)\(\lnot F = T\)\(\lnot U = U\)

给定 \(m\) 次操作,每次操作有三种类型:

  1. \(x_i \gets v,v \in \{ T,F,U \}\)
  2. \(x_i \gets x_j\)
  3. \(x_i \gets \lnot x_j\)

将这 \(m\) 次操作进行完了之后,\(n\) 个逻辑变量 \(\{ x_i \}_1^n\) 都与原值相同,问原 \(n\) 个逻辑变量 \(\{ x_i \}_1^n\) 中最少可以有几个 \(U\)


分析

首先,如果把 \(U\) 这个值去掉,那么这就变成了一道经典的并查集入门题——扩展域并查集判定解的合法性。那这里我们也考虑从扩展域并查集入手。

设扩展域并查集 \(D\)\(f_{i,1/0}\) 表示 \(x_i\)\(T/F\) 这个元素是否合法。我们在处理完 \(m\) 个操作后直接在扩展域上连边,最后处理出来后,在同一个集合内的元素合法性全部一致。

从“扩展域并查集判定解的合法性”这个问题来改进,如果解不合法,那么我们把它转化成本题中的 \(U\) 变量就可以了。也就是说,如果 \(f_{i,0}\)\(f_{i,1}\) 最后属于一个集合,那么取 \(T/F\) 都不合适,那么只能取 \(U\)​。

排除了显然不行的集合后,我们再来看其它的集合是不是取 \(T/F\) 都不合适。

首先从原并查集 \(D\) 来看,如果我们在每个 \(f_{i,0}\)\(f_{i,1}\) 上再连上一条边,形成的新图 \(D'\) 中每个连通分量要么全部都取 \(U\),要么全都不选 \(U\),这点从 \(\lnot\) 运算符的结果即可看出。

那么我们在 \(D\) 中把每个连通分量缩点,然后再重新在每个 \(f_{i,0}\)\(f_{i,1}\) 上再连上一条边,形成新图 \(D'\),这时就很好判断了,我们发现在 \(D'\) 中,每个连通块能形成一个合法的全都不选 \(U\) 的解当且仅当这个连通块内部能够二分图染色,否则就会有两个值相同的点相邻,不满足逻辑非运算的规则。

那么我们只要在满足上述合法性条件的情况下把能不赋值为 \(U\) 的都赋值为其它两个值即可。


实现

我们可以采用并查集缩点,也可以使用 2-SAT 配合 Tarjan 强连通分量缩点,那这两者有什么区别呢?

  1. 并查集:

    使用范围:限于限制关系是双向的约束系统,抽象到图论中只能是双向边。

    时间复杂度:通常为 \(O(n\log_2{n})\)\(O(n\alpha(n))\)

    代码复杂度:简单易写。

  2. 2-SAT 配合 Tarjan 强连通分量:

    使用范围:限制关系是单向或双向的约束系统,抽象到图论中可以是单向边或双向边。

    时间复杂度:通常为 \(O(n)\)

    代码复杂度:码量较大。

看的出来,并查集的复杂度和范围都不及 2-SAT 配合 Tarjan 强连通分量缩点,但是它好写、好调,可以在考场上留出更多时间给我们思考其他题目。


代码

时间复杂度:\(O(n\log_2{n})\),空间复杂度:\(O(n)\)

//#define Plus_Cat "tribool"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(1e5+10);
char opt[5];
int ID,Cas,n,m,ans,T,F,U;
int typ[N],ty[N<<1];
struct DSU {
	int fa[N<<1];
	void Init(int n) {
		FOR(i,1,n)fa[i]=i;
	}
	int get(int x) {
		return fa[x]^x?fa[x]=get(fa[x]):x;
	}
	void merge(int u,int v) {
		fa[get(u)]=get(v);
	}
} dsu;
bool vis[N<<1],col[N<<1],mark[N<<1];
vector<int> g[N<<1];
bool dfs(int u) {
	vis[u]=1;
	if(ty[u]==U)return 0;
	for(const int &v:g[u]) {
		if(vis[v]) {
			if(col[u]==col[v])return 0;
		} else {
			col[v]=col[u]^1;
			if(!dfs(v))return 0;
		}
	}
	return 1;
}
void Color(int u) {
	mark[u]=1,ty[u]=U;
	for(const int &v:g[u])if(!mark[v])Color(v);
}
int Cmain() {
	scanf("%d%d",&n,&m),ans=0,T=n+1,F=-(n+1),U=0;
	FOR(i,1,n)typ[i]=i;
	while(m--) {
		int i,j;
		scanf("%s",opt);
		if(opt[0]=='+')scanf("%d%d",&i,&j),typ[i]=typ[j];
		else if(opt[0]=='-')scanf("%d%d",&i,&j),typ[i]=-typ[j];
		else scanf("%d",&i),typ[i]=(opt[0]=='U'?U:(opt[0]=='T'?T:F));
	}
	RCL(vis+1,0,bool,n<<1),RCL(col+1,0,bool,n<<1),RCL(mark+1,0,bool,n<<1),RCL(ty+1,-INF,int,n<<1);
	dsu.Init(n<<1);
	FOR(i,1,n)if(typ[i]!=F&&typ[i]!=T&&typ[i]!=U)dsu.merge(i,typ[i]>0?typ[i]:n-typ[i]);
	FOR(i,1,n)if(typ[i]==U||dsu.get(i)==dsu.get(n+i))ty[dsu.get(i)]=ty[dsu.get(n+i)]=U;
	FOR(i,1,n)if(dsu.get(i)!=dsu.get(n+i))
		g[dsu.get(i)].push_back(dsu.get(n+i)),g[dsu.get(n+i)].push_back(dsu.get(i));
	FOR(i,1,n<<1)if(dsu.get(i)==i&&!vis[i]&&!dfs(i))Color(i);
	FOR(i,1,n)if(ty[dsu.get(i)]==U||ty[dsu.get(n+i)]==U)++ans;
	printf("%d\n",ans);
	FOR(i,1,n<<1)g[i].clear();
	return 0;
}
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	for(scanf("%d%d",&ID,&Cas); Cas; --Cas)Cmain();
	return 0;
}

反思

优点

  1. 中途在慌乱过后,迅速调整好了状态,并且思路清晰地打出了正解。

缺点

  1. 在写这道题的时候,一开始想到了并查集缩点再二分图染色,但是后来迷失在自己的“头脑风暴”中无法自拔,打了好多没用的部分分;
  2. 中途因为花了太多时间,导致后面有些许的慌乱;
  3. 题目信息和思路没有梳理好导致的失误,下次一定要把梳理工作做好。
posted @ 2024-11-22 21:27  Add_Catalyst  阅读(149)  评论(0)    收藏  举报