Skip to content

2-SAT 笔记

本文于 2026-05-12 重写。

一、定义

是或, 是与。

给定长度为 m 的序列 p,q,求一组合法的 n0/1 变量 x1n,满足

i=1m(xpixqi)=1

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

二、解决方法

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

  1. xy=1

x0y1y0x1

  1. ¬xy=1

x1y1y0x0

  1. x¬y=1

x0y0y1x1

  1. ¬x¬y=1

x1y0y1x0

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

xa1yb1yb1xa1

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

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

证明如下:

考虑对于 xy=1 的建边操作,本质为 ¬xy¬yx

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

又由建边的对称关系,存在路径 ¬y¬xy¬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 号点及其左边的点去,所以将 23 号点对应的公交站 1011 号点和 78 号点连边。

剩下的点同理:

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

最近更新