我们不妨把三种要求设为 a,b,c,变量前加 \neg 表示「不」,即「假」。上述条件翻译成布尔方程即:(\neg a\vee b\vee\neg c) \wedge (a\vee b\vee\neg c) \wedge (\neg a\vee\neg b\vee c)。其中,\vee 表示或,\wedge 表示与。(就像集合中并集交集一样)
现在要做的是,为 ABC 三个变量赋值,满足三位学生的要求。
Q: 这可怎么赋值啊?暴力?
A: 对,这是 SAT 问题,已被证明为 NP 完全 的,只能暴力。
Q: 那么 2-SAT 是什么呢?
A: 2-SAT,即每位同学 只有两个条件(比如三位同学都对大括号是否换行不做要求,这就少了一个条件)不过,仍要使所有同学得到满足。于是,以上布尔方程当中的 c,\neg c 没了,变成了这个样子:(\neg a\vee b) \wedge (a\vee b) \wedge (\neg a\vee\neg b)
怎么求解 2-SAT 问题?
使用强连通分量。 对于每个变量 x,我们建立两个点:x, \neg x 分别表示变量 x 取 true 和取 false。所以,图的节点个数是两倍的变量个数。在存储方式上,可以给第 i 个变量标号为 i,其对应的反值标号为 i + n。对于每个同学的要求 (a \vee b),转换为 \neg a\rightarrow b\wedge\neg b\rightarrow a。对于这个式子,可以理解为:「若 a 假则 b 必真,若 b 假则 a 必真」然后按照箭头的方向建有向边就好了。综上,我们这样对上面的方程建图:
原式
建图
\neg a\vee b
a\rightarrow b\wedge\neg b\rightarrow\neg a
a \vee b
\neg a\rightarrow b\wedge\neg b\rightarrow a
\neg a\vee\neg b\space \space
a\rightarrow\neg b\wedge b\rightarrow\neg a
于是我们得到了这么一张图:
可以看到,\neg a 与 b 在同一强连通分量内,a 与 \neg b 在同一强连通分量内。同一强连通分量内的变量值一定是相等的。也就是说,如果 x 与 \neg x 在同一强连通分量内部,一定无解。反之,就一定有解了。
但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?
这个很简单,只需要 当 x 所在的强连通分量的拓扑序在 \neg x 所在的强连通分量的拓扑序之后取 x 为真 就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 color[x] < color[-x] 。