其他分享
首页 > 其他分享> > 2-SAT

2-SAT

作者:互联网

主要内容

\(\operatorname{2~-~SAT}\),简单的说就是给出 \(n\) 个集合,每个集合有两个元素,已知若干个 \(<a,b>\) ,表示 \(a\) 与 \(b\) 矛盾(其中 \(a\) 与 \(b\) 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 \(n\) 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可 。

解决方法

使用强连通分量。

存图

对于每个变量 \(x\),我们建立两个点:\(x, \neg x\) 分别表示变量 \(x\) 取 true 和取 false。所以, 图的节点个数是两倍的变量个数 。在存储方式上,可以给第 \(i\) 个变量标号为 \(i\),其对应的反值标号为 \(i + n\)。

建图

边 \(u \rightarrow v\) 表示 「若 \(u\) 则 \(v\)」,其逆否命题为 \(\neg v \rightarrow \neg u\),表示「若 \(v\) 则 \(u\)」。对于每条二元限制,将其对应的命题与逆否命题的边连上,缺一不可。

  1. 对于 $a_i = \operatorname{true} $ 连边 $ \neg a_i \rightarrow a_i $ 「可以理解为:表示如果 \(i\) 选了 false 那么 \(i\) 必须要是 true 与 \(i\) 只能选一个 true 或者 false 矛盾,强迫 \(i\) 只能是 true」。

  2. 对于 $a_i = \operatorname{false} $ 连边 $ a_i \rightarrow \neg a_i $

  3. 对于 $ a \operatorname{or} b = \operatorname{true}$,连边 $\neg a\rightarrow b $ 和 $ \neg b\rightarrow a$ 「可以理解为:若 \(a\) 假则 \(b\) 必真,若 \(b\) 假则 \(a\) 必真」。

  4. 对于 $ a \operatorname{or} b = \operatorname{false}$,连边 $a\rightarrow \neg a $ 和 $ b\rightarrow \neg b$

  5. 对于 $ a \operatorname{and} b = \operatorname{false}$,连边 $a\rightarrow \neg b $ 和 $ b\rightarrow \neg a$

  6. 对于 $ a \operatorname{and} b = \operatorname{true}$,连边 $ \neg a\rightarrow a $ 和 $ \neg b\rightarrow b$

  7. 对于 $ a \ne b $,连边 $a \rightarrow \neg b $ 、 $ \neg a \rightarrow b $ 、 $b \rightarrow \neg a $ 、 $ \neg b \rightarrow a $

  8. 对于 $ a = b $,连边 $ a \rightarrow b $ 、 $ \neg a \rightarrow \neg b $ 、 $b \rightarrow a $ 、 $ \neg b \rightarrow \neg a $

可以看到,同一强连通分量内的变量值一定是相等的。也就是说,如果 \(x\) 与 \(\neg x\) 在同一强连通分量内部,一定无解。反之,就一定有解了。

但是,对于一组布尔方程,可能会有多组解同时成立。判断给每个布尔变量赋的值,只需要 当 \(x\) 所在的强连通分量的拓扑序在 \(\neg x\) 所在的强连通分量的拓扑序之后取 \(x\) 为真就可以了。

注:在使用 \(\operatorname{Tarjan}\) 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 color[x] > color[-x]

时间复杂度:\(O(n + m)\) ,即 \(\operatorname{Tarjan}\) 的时间复杂度 。

核心代码:

void tarjan(int u)
{
	 dfn[u]=low[u]=++Time; s.push(u),ins[u]=true;
	 for(int i=hea[u];i;i=nex[i])
	 {
	 	 if(!dfn[ver[i]]) tarjan(ver[i]),low[u]=min(low[u],low[ver[i]]);
	 	 else if(ins[ver[i]]) low[u]=min(low[u],dfn[ver[i]]);
	 }
	 if(dfn[u]==low[u])
	 {
	 	 sum++;
	 	 do u=s.top(),s.pop(),ins[u]=false,belong[u]=sum;
		 while(dfn[u]!=low[u]);
	 }
}

for(int i=1;i<=n*2;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++) if(belong[i*2-1]==belong[i*2]) exist=false;

标签:连边,neg,low,SAT,true,operatorname,rightarrow
来源: https://www.cnblogs.com/EricQian/p/15242749.html