什么是 2-SAT ?
SAT 是 Satisfiability 的缩写,意为可满足性。
n-SAT 问题即对一串 bool 变量进行赋值,满足 n 元 bool 方程组。
当 n≥3 时,n-SAT 问题已被证明是NP完全的,暂时没有多项式复杂度的做法;
但今天的主角 2-SAT 是可以在线性时间复杂度内解决的。
如何求解 2-SAT 问题?
转化为图论问题。
具体的,对于每个变量 x ,我们建立两个点 x 与 ¬x ,分别表示 x 取 true
或 false
。对于每个要求 (a∨b) ,连接 ¬a→b 和 ¬b→a ,表示“ a 为假 b 必为真,b 为假 a 必为真”。
建好图后,我们发现若点 x 为真,则点 x 可到达的点必然都为真。显然的,若点 x 与 ¬x 可互达,此问题必然无解,毕竟一个值不可能同时即为真又为假 薛定谔的bool(不是) 。稍作思考后,我们发现同一 强连通分量 内的点的值必然相等,因为均可互达。
到这里,我们就可以愉快的缩点啦!
顺带一提,缩点我使用了 kosaraju 算法,因为这样缩出的点已经按拓扑序排序,而 tarjan 求出的是逆拓扑序的。
code
大量使用三目运算符,极度不适
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void add(int x, int y) { g[x].push_back(y), r[y].push_back(x); } void dfs1(int x) { vis[x] = true; for (auto i : g[x]) !vis[i] ? dfs1(i) : void(); s.push_back(x); } void dfs2(int x) { bel[x] = cnt; for (auto i : r[x]) !bel[i] ? dfs2(i) : void(); } void kosaraju() { for (int i = 1; i <= n << 1; i++) !vis[i] ? dfs1(i) : void(); for (auto i = s.rbegin(); i != s.rend(); i++) !bel[*i] ? cnt++, dfs2(*i) : void(); }
|
缩点之后,如何得出每个点的取值呢?
我们有以下结论:一定选择拓扑序更大的。
考虑证明。设变量 xi 对应的节点为 ui,p ,p∈{0,1}
引理
根据建图规则,若存在边 (ui,p,uj,q) ,则必然存在边 (uj,q xor 1,ui,p xor 1) 。
更进一步的,若存在路径 (ui,p,uj,q) ,必然存在路径 (uj,q xor 1,ui,p xor 1) 。
证明
考虑反证法。
记 f(ui,p) 表示点 ui,p 缩点后所在点的拓扑序。
设有 f(ui,p)>f(ui,p xor 1) , f(uj,q)<f(uj,q xor 1) 且存在路径 (ui,p,uj,q) ,这样由我们的算法会得出错误结果。
然而,因为存在路径 (ui,p,uj,q) ,所以 f(ui,p)≤f(uj,q) ;存在路径 (ui,p,uj,q) ,故存在路径 (uj,q xor 1,ui,p xor 1) ,所以 f(uj,q xor 1)≤f(ui,p xor 1) 。联立后,明显产生矛盾,说明这种情况不存在。
算是证毕了吧?
洛谷P4782 【模板】2-SAT 问题
code
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
| #include <bits/stdc++.h> using namespace std; int n, m, bel[2000010], cnt; vector<int> g[2000010], r[2000010], s; bool vis[2000010]; void add(int x, int y) { g[x].push_back(y), r[y].push_back(x); } void dfs1(int x) { vis[x] = true; for (auto i : g[x]) !vis[i] ? dfs1(i) : void(); s.push_back(x); } void dfs2(int x) { bel[x] = cnt; for (auto i : r[x]) !bel[i] ? dfs2(i) : void(); } void kosaraju() { for (int i = 1; i <= n << 1; i++) !vis[i] ? dfs1(i) : void(); for (auto i = s.rbegin(); i != s.rend(); i++) !bel[*i] ? cnt++, dfs2(*i) : void(); } int main() { scanf("%d%d", &n, &m); while (m--) { int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d); add(a + !b * n, c + d * n), add(c + !d * n, a + b * n); } kosaraju(); for (int i = 1; i <= n; i++) if (bel[i] == bel[i + n]) { printf("IMPOSSIBLE\n"); return 0; } printf("POSSIBLE\n"); for (int i = 1; i <= n; i++) { printf("%d ", bel[i] < bel[i + n]); } return 0; }
|