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\)」。对于每条二元限制,将其对应的命题与逆否命题的边连上,缺一不可。
-
对于 $a_i = \operatorname{true} $ 连边 $ \neg a_i \rightarrow a_i $ 「可以理解为:表示如果 \(i\) 选了
false
那么 \(i\) 必须要是true
与 \(i\) 只能选一个true
或者false
矛盾,强迫 \(i\) 只能是true
」。 -
对于 $a_i = \operatorname{false} $ 连边 $ a_i \rightarrow \neg a_i $
-
对于 $ a \operatorname{or} b = \operatorname{true}$,连边 $\neg a\rightarrow b $ 和 $ \neg b\rightarrow a$ 「可以理解为:若 \(a\) 假则 \(b\) 必真,若 \(b\) 假则 \(a\) 必真」。
-
对于 $ a \operatorname{or} b = \operatorname{false}$,连边 $a\rightarrow \neg a $ 和 $ b\rightarrow \neg b$
-
对于 $ a \operatorname{and} b = \operatorname{false}$,连边 $a\rightarrow \neg b $ 和 $ b\rightarrow \neg a$
-
对于 $ a \operatorname{and} b = \operatorname{true}$,连边 $ \neg a\rightarrow a $ 和 $ \neg b\rightarrow b$
-
对于 $ a \ne b $,连边 $a \rightarrow \neg b $ 、 $ \neg a \rightarrow b $ 、 $b \rightarrow \neg a $ 、 $ \neg b \rightarrow a $
-
对于 $ 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