#C240812B.「NOIP2023」三值逻辑
标签(Label)
- 图论转化
- 深搜
前言(Front talk)
$\qquad$去年没有做出来主要是两个问题:1.考场上太急,没有认真分析就开始做;2.没有细心模拟样例。
$\qquad$这道题确实是转化为图论来做,但是由于没有分析好样例,导致我那时抓着半截就走,从一开始方向就错了。
网址(Website)
题目(Problem)
题目描述
小 L 今天学习了 Kleene 三值逻辑。
在三值逻辑中,一个变量的值可能为:真($\mathit{True}$,简写作 $\mathit{T}$)、假($\mathit{False}$,简写作 $\mathit{F}$)或未确定($\mathit{Unknown}$,简写作 $\mathit{U}$)。
在三值逻辑上也可以定义逻辑运算。由于小 L 学习进度很慢,只掌握了逻辑非运算 $\lnot$,其运算法则为:
$$\lnot \mathit{T} = \mathit{F}, \lnot \mathit{F} = \mathit{T}, \lnot\mathit{U} = \mathit{U}.$$
现在小 L 有 $n$ 个三值逻辑变量 $x_1,\cdots, x_n$。小 L 想进行一些有趣的尝试,于是他写下了 $m$ 条语句。语句有以下三种类型,其中 $\leftarrow$ 表示赋值:
- $x_i \leftarrow v$,其中 $v$ 为 $\mathit{T}, \mathit{F}, \mathit{U}$ 的一种;
- $x_i \leftarrow x_j$;
- $x_i \leftarrow \lnot x_j$。
一开始,小 L 会给这些变量赋初值,然后按顺序运行这 $m$ 条语句。
小 L 希望执行了所有语句后,所有变量的最终值与初值都相等。在此前提下,小 L 希望初值中 $\mathit{Unknown}$ 的变量尽可能少。
在本题中,你需要帮助小 L 找到 $\mathit{Unknown}$ 变量个数最少的赋初值方案,使得执行了所有语句后所有变量的最终值和初始值相等。小 L 保证,至少对于本题的所有测试用例,这样的赋初值方案都必然是存在的。
输入格式
本题的测试点包含有多组测试数据。
输入的第一行包含两个整数 $c$ 和 $t$,分别表示测试点编号和测试数据组数。对于样例,$c$ 表示该样例与测试点 $c$ 拥有相同的限制条件。
接下来,对于每组测试数据:
- 输入的第一行包含两个整数 $n$ 和 $m$,分别表示变量个数和语句条数。
- 接下来 $m$ 行,按运行顺序给出每条语句。- 输入的第一个字符 $v$ 描述这条语句的类型。保证 $v$ 为 TFU+-的其中一种。
- 若 $v$ 为 TFU的某一种时,接下来给出一个整数 $i$,表示该语句为 $x_i \leftarrow v$;
- 若 $v$ 为 +,接下来给出两个整数 $i,j$,表示该语句为 $x_i \leftarrow x_j$;
- 若 $v$ 为 -,接下来给出两个整数 $i,j$,表示该语句为 $x_i \leftarrow \lnot x_j$。
 
- 输入的第一个字符 $v$ 描述这条语句的类型。保证 $v$ 为 
输出格式
对于每组测试数据输出一行一个整数,表示所有符合条件的赋初值方案中,$\mathit{Unknown}$ 变量个数的最小值。
样例 #1
样例输入 #1
1 3
3 3
- 2 1
- 3 2
+ 1 3
3 3
- 2 1
- 3 2
- 1 3
2 2
T 2
U 2样例输出 #1
0
3
1提示
【样例解释 #1】
第一组测试数据中,$m$ 行语句依次为
- $x_2 \leftarrow \lnot x_1$;
- $x_3 \leftarrow \lnot x_2$;
- $x_1 \leftarrow x_3$。
一组合法的赋初值方案为 $x_1 = \mathit{T}, x_2 = \mathit{F}, x_3 = \mathit{T}$,共有 $0$ 个$\mathit{Unknown}$ 变量。因为不存在赋初值方案中有小于 $0$ 个$\mathit{Unknown}$ 变量,故输出为 $0$。
第二组测试数据中,$m$ 行语句依次为
- $x_2 \leftarrow \lnot x_1$;
- $x_3 \leftarrow \lnot x_2$;
- $x_1 \leftarrow \lnot x_3$。
唯一的赋初值方案为 $x_1 = x_2 = x_3 = \mathit{U}$,共有 $3$ 个$\mathit{Unknown}$ 变量,故输出为 $3$。
第三组测试数据中,$m$ 行语句依次为
- $x_2 \leftarrow \mathit{T}$;
- $x_2 \leftarrow \mathit{U}$;
一个最小化 $\mathit{Unknown}$ 变量个数的赋初值方案为 $x_1 = \mathit{T}, x_2 = \mathit{U}$。$x_1 = x_2 = \mathit{U}$ 也是一个合法的方案,但它没有最小化 $\mathit{Unknown}$ 变量的个数。
【样例解释 #2】
该组样例满足测试点 $2$ 的条件。
【样例解释 #3】
该组样例满足测试点 $5$ 的条件。
【样例解释 #4】
该组样例满足测试点 $8$ 的条件。
【数据范围】
对于所有测试数据,保证:
- $1 \le t \le 6$,$1 \le n,m \le 10 ^ 5$;
- 对于每个操作,$v$ 为 TFU+-中的某个字符,$1 \le i,j \le n$。
| 测试点编号 | $n,m\leq$ | $v$ 可能的取值 | 
|---|---|---|
| $1,2$ | $10$ | $\mathit{TFU+-}$ | 
| $3$ | $10^3$ | $\mathit{TFU}$ | 
| $4$ | $10^5$ | $\mathit{TFU}$ | 
| $5$ | $10^3$ | $\mathit{U+}$ | 
| $6$ | $10^5$ | $\mathit{U+}$ | 
| $7$ | $10^3$ | $\mathit{+-}$ | 
| $8$ | $10^5$ | $\mathit{+-}$ | 
| $9$ | $10^3$ | $\mathit{TFU+-}$ | 
| $10$ | $10^5$ | $\mathit{TFU+-}$ | 
题解(Solution)
$\qquad$分析样例以及数据范围,可以发现每个值最终的结果只和最后一次操作有关,这个地方很容易误导我们直接倒着做,但是中间被覆盖的操作虽然对当前位置没有影响,却会影响到其他的位置,因此,到这座反而更加的麻烦。
$\qquad$考虑正向分析,这里用了样例二中的第二组数据:
1 1
10 10
- 7 6
+ 4 1
+ 6 4
T 1
+ 2 9
- 9 10
U 10
+ 5 5
U 8
T 3
$\qquad$注意图中的 $x_6$ 位置,由于其被赋值为 $x_4$ ,而此时的 $x_4$ 的值为 $x1$ ,因此我们可以直接将其赋值为 $x_1$ 。可见,最终每个 $x_i$ 值只有 $5$ 中情况:$T$、$F$、$U$、$x_j$、$!x_j$ 。
$\qquad$发现位置之间的关系可以转化为图上点与点之间的关系,于是我们考虑依照最后的关系建无向图,建出来的图最终会是几个连通块的形式。对于每个连通块,如果里面存在 $T$ 、$F$ 中的任意一个,那么这个图中的所有点都一定是 $T$ 、$F$;如果存在 $U$ ,那么所有的点都是 $U$ ;如果都没有,那么我们可以尝试令其中任意一个点为 $T$ ,若最终没有矛盾,那么这个连通块里面的所有点都可以成为$T$或$F$,否则只能全部为 $U$ 。因为题目保证一定有解,出现$T$ 、$F$的联通块中一定不会有$U$,且出现$U$的联通块中一定不会有$T$ 、$F$。
$\qquad$时间复杂度:$O(n+m)$
$\qquad$后补:对于如何建边,以下标为 $x,y$ ,是否取反为边权 $v$ ,搜索时只需要看边权 $v$ ,就知道 $x$ 、$y$ 两点是相同还是相反了。
代码(Code)
#include<bits/stdc++.h>
#include<vector> 
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Rof(i,l,r) for(int i=l;i>=r;i--)
using namespace std;
#define P pair<int,int>
#define int long long
#define fi first
#define se second
inline int rd(){int x;return cin>>x,x;}
const int inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2012345;
vector<P> ft[N];
int n,m,ans,a[N]; 
#define T (n+1)
#define F (n+2)
#define U (n+3)
inline int f(int x){
	if(x==T) return F;else
	if(x==F) return T;else
	if(x==U) return U;
	else return -x;
}
bitset<N> vst;
int siz[N];
inline void dfs_col(int x){
	siz[x] = vst[x] = true;
	for(auto p:ft[x]){
		int y = p.fi, v = p.se;
		if(a[y]) assert(v?a[x]==a[y]:a[x]==f(a[y]));
		else{
			a[y] = v ? a[x] : f(a[x]);
			dfs_col(y);
			siz[x] += siz[y];
		}
	}
}
inline bool dfs_nxt(int x){
	siz[x] = vst[x] = true;
	bool flg = false;
	for(auto p:ft[x]){
		int y = p.fi, v = p.se;
		if(a[y]){
			int chk = v ? a[x] : f(a[x]);
			if(chk!=a[y]) flg = true;
		}else{
			a[y] = v ? a[x] : f(a[x]);
			flg |= dfs_nxt(y);
			siz[x] += siz[y];
		}
	}return flg;
}
//多测清空 
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int Tim = clock(), tid=rd(), TT=rd();
	cerr<<"TID:"<<tid<<'\n';
	while(TT--){
		cin>>n>>m;
		
		fill(siz,siz+n+1,0);vst.reset();
		fill(a,a+n+1,0);ans = 0;
		For(i,1,n) ft[i].clear();
		
		while(m--){
			char v;cin>>v;
			if(v=='+'||v=='-'){
				int i,j;cin>>i>>j;
				if(a[j]) a[i] = v=='+' ? a[j] : f(a[j]);
				else	 a[i] = v=='+' ? j : -j;
			}else{
				int i = rd();
				switch(v){
					case 'T':{a[i] = T;break;}
					case 'F':{a[i] = F;break;}
					case 'U':{a[i] = U;break;}
				}
			}
		}For(i,1,n) cerr<<a[i]<<' ';cerr<<'\n';
		//以点建边 
		For(i,1,n){
			int x = i, y = abs(a[i]);
			if(1<=y && y<=n){
				if(a[i]>0){
					ft[x].emplace_back(y,1);
					ft[y].emplace_back(x,1);
				}else
				if(a[i]<0){
					ft[x].emplace_back(y,0);
					ft[y].emplace_back(x,0);
				}a[i] = 0;//将颜色调为 0  
			}
		}
		
		For(x,1,n) if(a[x]>n && !vst[x])  dfs_col(x), ans += a[x]==U?siz[x]:0;
		For(x,1,n) if(a[x]<=n){
			a[x]=T;
			if(dfs_nxt(x)) 
				ans += siz[x];	
		}
		
		cout<<ans<<'\n';
	}return cerr<<"TIME:"<<(clock()-Tim)/1000.,0;
}

 
 