简介
SAT(Satisfiability) 问题是求使得形如 xi∨xj∨¬xk 的多个条件式同时成立的一组取值,当每个条件的变量个数大于 2 时已经被证明无法用多项式的复杂度求解,但是如果只有 2 个可以在线性时间内求出可行解。
模板
给定 n 个未赋值的布尔变量 x1∼xn,再给 m 个条件,每个条件形如 xi=1,0∨xj=1,0,现在求一个合法解满足所有条件。
题目链接:AcWing 2402,P4782。
在数理逻辑中,命题 a→b 的真值表如下:
a |
b |
a→b |
0 |
0 |
1 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
1 |
1 |
所以 a→b 等价于 ¬a∨b,反过来看也就是 a∨b 等价于 ¬a→b 也等价于 ¬b→a,我们可以把 → 看作有向图的边,然后用 Tarjan 算法缩点。
判断是否产生矛盾的方法是看 x,¬x 是否在同一个强连通分量中,如果在说明无解。
对于每一个命题 x,都比较 x,¬x 强连通分量的拓扑序,选择靠后的那个,原因如下:
-
若 a,b 处在同一强连通分量中,那么 ¬a,¬b 也一定处在同一强连通分量中。
-
若 a 的拓扑序更靠前,设存在 p→a,¬a→¬p,用整值函数 φ(x) 表示拓扑序,这里参考电势,我不知道是否存在一个更常用的符号用来表示拓扑序。数值小表示靠前,那么:
φ(a)φ(p)φ(¬p)∴φ(p)<φ(¬a)≤φ(a)≥φ(¬a)<φ(¬p)
此时选择靠后 ¬a 的是一定可以构造出合法解的。
-
若 a 的拓扑序更靠前,设存在边 a→q,¬q→¬a,首先假设 a,q 不在同一强连通分量中,由于不存在矛盾:
φ(q)φ(¬q)∵φ(q)∴φ(a)=φ(a)+1=φ(¬a)−1=φ(¬q)=φ(¬a)−2
如果 φ(a)=φ(¬a)−1,那么 q 必然与 a 处在同一强连通分量中,题设就不成立了,所以:
φ(a)φ(a)+1φ(q)≤φ(¬a)−3≤φ(¬a)−2<φ(¬q)
此时也是选择靠后的仍然是合法解。
所以我们就选拓扑序靠后的,再加上 Tarjan 算法求出来的强连通分量编号与拓扑序存在逆相关的关系,所以我们就能得到求解 2-SAT 问题的代码了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <cstdio> #include <cstring> #include <stack> #include <algorithm> using namespace std;
const int N = 2000010, M = N; int h[N], e[M], ne[M], idx; int id[N], dfn[N], low[N], timestamp, scc_cnt; stack<int> stk; bool instk[N];
int n, m;
void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void tarjan(int u) { dfn[u] = low[u] = ++timestamp; instk[u] = true; stk.push(u); for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!dfn[j]) { tarjan(j); low[u] = min(low[u], low[j]); } else if (instk[j]) { low[u] = min(low[u], dfn[j]); } } if (dfn[u] == low[u]) { int y; ++scc_cnt; do { y = stk.top(); stk.pop(); id[y] = scc_cnt; instk[y] = false; } while (y != u); } }
int get(int v, int t) { return 2*v + t; }
bool check() { for (int i = 1; i <= n; i++) if (id[get(i, 0)] == id[get(i, 1)]) return false; return true; }
int main() { memset(h, -1, sizeof h); scanf("%d%d", &n, &m); while (m--) { int i, a, j, b; scanf("%d%d%d%d", &i, &a, &j, &b); add(get(i, a^1), get(j, b)); add(get(j, b^1), get(i, a)); } for (int i = 2; i <= 2*n+1; i++) if (!dfn[i]) tarjan(i); if (!check()) puts("IMPOSSIBLE"); else { puts("POSSIBLE"); for (int i = 1; i <= n; i++) { if (id[get(i, 0)] < id[get(i, 1)]) printf("0 "); else printf("1 "); } puts(""); } return 0; }
|