Skip to content

2-SAT 笔记 ​

本文于 2026-05-12 重写。

一、定义 ​

∨ 是或,∧ 是与。

给定长度为 m 的序列 p,q,求一组合法的 n 个 0/1 变量 x1…n,满足

⋀i=1m(xpi∨xqi)=1

这个问题可以用 2-SAT 解决。

二、解决方法 ​

我们把一个点 x 拆成两个状态:x0 表示 x=0,x1 表示 x=1。在题目给出的限制中,显然这些状态是可以互相推理的。

  1. x∨y=1

x0⇒y1,y0⇒x1。

  1. ¬x∨y=1

x1⇒y1,y0⇒x0。

  1. x∨¬y=1

x0⇒y0,y1⇒x1。

  1. ¬x∨¬y=1

x1⇒y0,y1⇒x0。

如果用 a 表示 x=a 时原式一定为 1,b 表示 y=b 时原式一定为 1,那么观察可以得到:

xa⊕1⇒yb∧1,yb⊕1⇒xa∧1。

我们根据推理情况建有向图,跑强连通分量,如果存在 x0 和 x1 在同一强连通分量内,则说明无解。

当 x1 所在的强连通分量的拓扑序在 x0 所在的强连通分量的拓扑序之后取 x=1,Kosaraju 算法按拓扑序排好的,Tarjan 算法则是反着的拓扑序,要将 sccx1>sccx0 改为 sccx1<sccx0。

证明如下:

考虑对于 x∨y=1 的建边操作,本质为 ¬x→y、¬y→x。

如果取点 x=1 和 ¬x=0 不可行,说明这样取或导致某个 y 同时必须取 y=0 和 y=1,即存在路径 x→y 和 x→¬y。

又由建边的对称关系,存在路径 ¬y→¬x 和 y→¬x,整理可得存在路径 x→¬x,即 x0 的强连通分量拓扑序比 x1 大。

cpp
cin >> n >> m;
for (int i = 1, x, a, y, b; i <= m; i++) {
    cin >> x >> a >> y >> b;
    add(x + n * (a & 1), y + n * (b ^ 1));
    add(y + n * (b & 1), x + n * (a ^ 1));
}
kosaraju();
for (int i = 1; i <= n; i++)
    if (scc[i] == scc[i + n]) {
        cout << "IMPOSSIBLE";
        return 0;
    }
cout << "POSSIBLE\n";
for (int i = 1; i <= n; i++)
    cout << (scc[i] > scc[i + n]) << " ";

三、建图优化 ​

如果有这么一张 n2 的图:

举 2 号点为例,发现它要这样连边:

所以弄两班公交车,一班向右走,一班向左走:

因为 2 号点要去 5 号点及其左边的点,所以他在 13 号点上向左走的公交车;7 号点应该要被 2 号点及其左边的点去,8 号点应该被 3 号点及其左边的点去,所以将 2,3 号点对应的公交站 10,11 号点和 7,8 号点连边。

剩下的点同理:

我们成功把边数优化到 O(n) 级别的(由于常数是 6,上图 n=4,所以看起来像是还更多边了,实则当 n 有一定大小时并不是)。

最近更新