2-SAT 总结

· · 算法·理论

近期学习了 2-SAT,于是来总结一下!!

:::warning[观前提示]{open} 建议先了解2-SAT 是什么和基本求解方式。 :::

UPD:感谢__NikaidoHiro__指出,sinz 的第三条子句 y_{i-1} 全部写少了一个 \neg,抱歉喵。

简要的基本知识

2-SAT 可以规约成图论问题,图上有向边即为命题的推出关系。可以使用 Tarjan 或 Kosaraju 等算法求 scc 并判断一个命题和其否命题是否在同一 scc 内,若在则等价,不合法。对于一个命题和它的否命题,可以通过定 DAG 上拓扑序靠后的命题为真来构造一组解,因为若一个命题为真 DAG 上它的后继都为真,为了保证解的合法性取一定不会发生冲突的靠后命题。

建图规则

建图的基本顺序是:

  1. 把约束写成若干“必须为真的子句”;
  2. 每个子句连边。

一些基本逻辑的推导

折叠框包裹,可以先思考(?)

按照推导顺序,从易到难。

1. 蕴含 a\Rightarrow b(“a 真则 b 真”)

::::success[答案] 连边:

a\to b,\neg b\to\neg a

:::warning[为什么有两条?]

::: :::: #### 2. NAND($\neg a\vee\neg b$,“$a$ 和 $b$ 不能同真”) ::::success[答案] 连边: $$ a\to\neg b,b\to\neg a $$ “一个真另一个必须为假”的直观理解。 这两个命题恰好互为逆否命题。 :::: #### 3. 析取($a\vee b$,逻辑或) ::::success[答案] 连边: $$ \neg a\to b,\neg b\to a $$ 其实就是反的 NAND,只不过 NAND 更直观一些。 :::: #### 4. 等价 $a\Leftrightarrow b

::::success[答案] 连边:

a\to b,b\to a,\neg a\to\neg b,\neg b\to\neg a
视作两个相互蕴含。

5. 异或 a\oplus b

::::success[答案] 连边:

a\to\neg b,b\to\neg a,\neg a\to b,\neg b\to a

NAND 加强版,不仅要两个不同真,也要两个不同假。

异或可以写作:

(a\vee b)\wedge(\neg a\vee\neg b)
于是拆成正反两个 NAND 即可。

6. 否定约束 a=\neg b

::::success[答案] 连边:

a\to\neg b,b\to\neg a,\neg a\to b,\neg b\to a

:::warning[你这不是和异或一样吗] 对的其实我没有写错,这两个东西就是等价的喵。可以简单推一下。 ::: ::::

7. 单位子句(强制取值)

::::success[答案] 关键结论:a 等价于 a\vee a。于是可以按照析取来做!

若强制为真连边:

\neg a\to a

:::warning[什么你说为啥反向不用连边] 恭喜!你和我犯了一样的错误。

首先肯定反向连边就变成 a\Leftrightarrow\neg a 直接不合法了,其次你仔细观察一下析取的式子就会发现推出来两条边是一样的()

若强制为否连边:

a\to\neg a
这个和强制为真是反的。

8. 合取(a\wedge b,逻辑与)

::::success[答案] 连边:

\neg a\to a,\neg b\to b
两个都强制为真,就是两个单位子句

有了以上知识,你就可以游刃有余解决 2-SAT 的基本建模了!

例题:https://codeforces.com/gym/104120/problem/E

2-SAT 全家桶!注意数据范围!

建图基础技巧

中转点

若有两个集合 S_1 和 S_2,对于所有 S_1 中的命题全部需要连向 S_2 中连边,可以加入中转点 p 传递推出的关系,具体做法是 S_1 中所有点连向 p,p 连向 S_2 中所有点。

链式约束

实际上就是省去一些冗余连边,白嫖命题推出关系的传递性,想的时候就应该考虑了,可能不算什么技巧。

拆拆拆大子句

析取大子句

(x_1\vee\cdots\vee x_k)\Rightarrow y\equiv\bigwedge_{i=1}^k\neg(x_i\wedge\neg y)=\bigwedge_{i=1}^k(\neg x_i\vee y)

常见公式,关键在于 (x_1\vee\cdots\vee x_k)\Rightarrow y\equiv\neg(x_1\vee\cdots\vee x_k)\vee y。中间一步的式子是容易直观理解的。

:::warning[y\Rightarrow(x_1\vee\cdots\vee x_k) 为何不行] 这个东西是 (k+1)-SAT,无法拆成二元子句。 :::

合取大子句

y\Rightarrow(x_1\wedge\cdots\wedge x_k)\equiv\bigwedge_{i=1}^k\neg(y\wedge\neg x_i)=\bigwedge_{i=1}^k(\neg y\vee x_i)

同样的,中间一句是容易理解的。

:::warning[不说反过来的是因为它是 k+1-SAT 吗] 对。 :::

sinz 编码

显然可以连 $\binom{k}{2}$ 个 NAND,但是这个太爆炸了是 $O(k^2)$ 的。 可以引入辅助变量 $y_i$ 表示 $x_1\dots x_i$ 中已经有真的,连三个子句组: $$ \neg x_i\vee y_i,\neg y_{i-1}\vee y_i,\neg x_i\vee\neg y_{i-1} $$ 分别含义为:若 $x_i$ 为真前缀一定存在真的,$y$ 单调传递(一旦变为真后继必须均为真),$x_i$ 为真和前面已为真不能同时。 如果把这三个写作合取为假的形式: $$ \neg(x_i\wedge\neg y_i),\neg(y_{i-1}\wedge\neg y_i),\neg(x_i\wedge y_{i-1}) $$ 是容易理解的。 ### 区间连边 即 $x\to y_j$,$j\in[l,r]$,并且存在多个 $x$ 和多组区间时,可以在 $y$ 上建立线段树,非 leaf 的节点作为区间辅助节点。这是中转点的推广技巧。若有单组约束产生 $O(\log k)$ ($k$ 为区间长度)。 或者两侧均为区间,可以简单做到 $O(\log^2 k)$,但是可以结合中转点做到 $O(\log k)$,即左侧非 leaf 点连向中转点再连向右侧非 leaf 点。