前言

2-SAT中的SAT是适定性(Satisfiability)问题的简称,一般形式为 $k$ - 适定性问题,简称 $k$ - SAT,但由于 $k>2$ 时问题为NP完全问题(只有指数级别复杂度的解法,或者多项式级别复杂度的相似解法),而 $k=1$ 时都不用解了 (废话) ,所以下面全部考虑 $k=2$ 的情况。

定义

2-SAT问题简单来说就是,有 $n$ 个集合,每个集合包含两个元素(集合 $i$ 包含元素 $2i-1$ 和 $2i$,但其实编号是无关紧要的,任意都行),你必须要在每个集合里分别选择刚好一个元素,但某些元素之间可能有矛盾,即这两个元素不能在一种方案里被同时选择,问你是否有解,报告出来,如果有解,输出一种方案(可能不用输出)。

题目

看上面的定义可能有点难懂,这里举个题目。

有一场宴会,这场宴会只有 $n$ 对幸运夫妻可以参加,每对夫妻里只能选择刚好一个人去参加这场宴会。

但部分人之间可能有矛盾,会给出所有有矛盾的两人编号。

问你,是否可以构造一种合法方案,如果不行,报告无解,否则输出一种方案。

解法

(下面统一默认要解决的是实际问题,不是定义里的问题)

定义点、边

我们考虑把夫妻编个名字:

  • 第一对夫妻:A男、A女
  • 第二对夫妻:B男、B女
  • 第三对夫妻:C男、C女
  • 以此类推

然后,我们考虑建图。

但这个建图就要用到一点思维了。

我们不以其他的定义定义点,我们就把一个点当做一个现实

比如:

  • $1$ 号点代表第一对夫妻是A男参与宴会,$2$ 号点代表A女参与宴会
  • $3$ 号点代表第二对夫妻是B男参与宴会,$4$ 号点代表B女参与宴会
  • $5$ 号点代表第三对夫妻是C男参与宴会,$6$ 号点代表C女参与宴会
  • 以此类推

边的定义也很难思考出:

  • 一条有向边 $u \to v$,代表现实 $u$ 满足了,现实 $v$ 也要满足

举个例子

所以,如果说A男和B女、C男和A女之间都有矛盾,那么就需要在:

  • A男、B男(如果A男参与宴会,那么因为有矛盾,B女就不能参与宴会,就需要B男去)
  • B女、A女(如果B女参与宴会,那么因为有矛盾,A男就不能参与宴会,就需要A女去)
  • C男、A男(如果C男参与宴会,那么因为有矛盾,A女就不能参与宴会,就需要A男去)
  • A女、C女(如果A女参与宴会,那么因为有矛盾,C男就不能参与宴会,就需要C女去)

之间连边。

换到图上就是要连:

  • $1 \to 3$
  • $4 \to 2$
  • $5 \to 1$
  • $2 \to 6$

建图后的处理、输出答案

我们可以先考虑对这个有向图做个SCC缩点。

根据SCC性质,以及边定义具有传递性的性质(如果 $a \to b$ 有边,$b \to c$ 有边,那么 $a \to c$ 之间也可以加边,而不影响答案),只要一个SCC中有一个现实满足了,其他现实都得满足。

所以,一个SCC里要么就是所有现实都不满足,要么就是全部满足,不可能有部分满足的情况,显然。

判断是否有解

于是乎,我们就判断一下,一个SCC里是否有同一对夫妻参与宴会的现实对应的两个点(可能有点难懂)。

如果有,那么根据上一行里写的性质,这一对夫妻要么都参与宴会,要么都不参与,永远不可能满足题目条件。

所以,我们只用循环每个SCC,然后:

  • 清空标记数组
  • 循环当前SCC内的每个点
    • 标记这个点对应夫妻编号
    • 如果在标记前,这对夫妻已经被标记过
      • 输出无解
  • 如果到最后都没有说无解
    • 输出有解

输出方案

而判断有解后如何构造一种方案输出呢?见下。

这个输出方案的思路非常妙,也比较难理解。

我们一对一对夫妻地考虑。

我们找到这一对夫妻里两个人对应的节点编号 $x$(男性去参加宴会)、$y$(女性去参加宴会),那么一个结论就是,把缩点后的图跑个拓扑排序,如果说 $x$ 所属SCC的拓扑序,比 $y$ 所属SCC的拓扑序,要大,那么就让 $x$ 对应现实满足(男性去参加宴会),否则就让 $y$ 对应现实满足(女性去参加宴会)。

由于SCC缩点的模板中,缩完点后,这张DAG的拓扑序就是所有SCC编号反向后的结果(设最大编号为 $n$,那么拓扑序就是 $[n,n-1,n-2,n-3,\dots,2,1]$),所以如果说 $x$ 所属SCC的编号比 $y$ 所属SCC的编号要小,那么就让男性去参加,否则让女性,显然。

为啥这样是对的呢?我们考虑证明一下:

性质1

(下面假设拓扑排序就是用的通用模板)

对于一个DAG,其拓扑排序的过程就是一个BFS的变种。

即,我们把这个DAG分层后,按层去往拓扑序序列里加点,得到的最终序列就是拓扑序。

一个节点被分到哪一层,全部取决于这个节点距离任意一个入度为 $0$ 的点的最长路的最大值;特殊地,本身入度就为 $0$ 的点被分到了第 $0$ 层。

有人问,为啥要用最长路作为编号,而不是最短路?原因见下。

由于是按照层去编号拓扑序的,所以这种分层方案必须要满足:

  • 对于每条边 $u \to v$,$u$ 所处层必须要严格小于 $v$ 所处层。

所以,我们其实就只需要证明,最长路满足上述条件,但最短路不满足


我们先证明最长路是正确的。

考虑反证法。

如果说对于某条边 $u \to v$,不满足上述条件,我们考虑证明这个现象不存在。

我们求出 $u$ 对应的层 $dis_u$,那么在跑Dijkstra(准确来说是SPFA)算法时,必然会执行语句 $dis_v=\max(dis_v,dis_u+1)$,显然。

所以,最终 $dis_v \geq dis_u+1$($dis_v>dis_u$)一定满足,显然。

这个一定满足的条件也就说明,$v$ 所在层($dis_v$),一定比 $u$ 所在层($dis_u$)要大。

也即,$u$ 所处层严格小于 $v$ 所处层。

与假设矛盾,证毕。


我们再证明最短路是错误的。

$dis_v=\max(dis_v,dis_u+1)$前面的过程先跳过,我们考虑语句 $dis_v=\max(dis_v,dis_u+1)$,在使用最短路时,会变成 $dis_v=\min(dis_v,dis_u+1)$。

所以,在使用最短路作为层数的时候,只能保证 $dis_v \leq dis_u+1$,不一定满足上述条件。

虽然说是不一定满足上述条件,但可能错误是不可改变的现实,所以最短路不可取,证毕。

如:

的分层方案就是:

所以拓扑序:(下面只是一种方案)

此时有人就问了,说这个有啥用?待会儿你就知道了。


性质2

有了这个结论的基础,我们就可以大胆猜想一个推论:

如果说 $x$ 点的拓扑序比 $y$ 点大,那么一定满足下面两个条件之一:

  • $x$ 和 $y$ 之间没有路径。
  • 有从 $y$ 到 $x$ 的有向路径,但没有反向路径(从 $x$ 到 $y$ 的路径)。

这个是很好证明对于任何拓扑序都满足的,所以此处略。


正式证明

(上面所有的 $x$、$y$ 都是设的变量,但下面的 $x$、$y$ 则是做法里的两个现实分别对应的编号)

有了这两个结论的基础,我们就可以开始证明这个做法的正确性了。

根据性质2,如果说 $x$ 点的拓扑序比 $y$ 点大,那么就有两种情况,只要分别证明即可。

对于情况1,两点之间没有路径,那么无论是 $x$ 点对应现实满足,还是 $y$ 点,都是可行的,证毕。

对于情况2,有从 $y$ 到 $x$ 的右项路径,但没有反向路径,那么根据连边具有传递性的性质,如果 $y$ 对应现实满足,$x$ 也要满足,不符合题目条件;但如果说 $x$ 对应现实满足,那么我们完全可以说 $y$ 对应现实不满足,此时就满足题目条件了,证毕。

两种情况都满足,证毕。

证明完后,直接按上述过程搞即可得到最终方案。