2-SAT
Posted zcr-blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2-SAT相关的知识,希望对你有一定的参考价值。
前置芝士:
dfs,强连通分量
一般的k-sat问题就是给你n个变量$a_i$,每个变量有k个取值,然后给你一堆条件让你求出满足所有条件的一组解。
而当k>2时已经被证明为NP完全问题,没有多项式复杂度的解法(只能暴搜),故我们只考虑2-sat问题。
2-sat问题就是每个变量只有两种取值(当做0和1),每次给你一个条件,形如若p则q,让你求一组满足所有条件的解。
我们可以把这种条件转换到dag上。
从p连向q一条边,这时如果从x能到y则说明可以从x推出y。
注意上述条件还包含其逆否命题:若非q则非p,题目有的时候并不会给你,所以你得加上。
求解2-sat问题有多种办法,其中一种是暴搜。
就是直接看看能不能从x推出非x或从非x推出x,然后去给x定取值。
暴搜模板题:P3007 [USACO11JAN]The Continental Cowngress G
我们只要枚举每个值是0还是1,然后看看能不能推出矛盾,若都不能,则答案是?。
#include <iostream> #include <cstdio> using namespace std; const int N = 2010; const int M = 8010; struct node{ int pre, to; }edge[M]; int head[N], tot; int n, m; int ans[N];//0: No 1: Yes 2: ? int mark[N]; void add(int u, int v) { edge[++tot] = node{head[u], v}; head[u] = tot; } void dfs(int x) { mark[x] = true; for (int i = head[x], y; i; i = edge[i].pre) if (!mark[y = edge[i].to]) dfs(y); } bool check(int x) { for (int i = 1; i <= n << 1; i++) mark[i] = 0; dfs(x); for (int i = 1; i <= n; i++) if (mark[i] && mark[i + n]) return false; return true; } bool solve() { for (int i = 1; i <= n; i++) { bool c1 = check(i); bool c2 = check(i + n); if (c1 && c2) ans[i] = 2; else if (c1 && !c2) ans[i] = 1; else if (!c1 && c2) ans[i] = 0; else return false; } return true; } int main() { scanf("%d%d", &n, &m); for (int i = 1, a, b, x, y; i <= m; i++) { char c, d; scanf("%d %c %d %c", &a, &c, &b, &d); x = (c == ‘Y‘ ? 1 : 0); y = (d == ‘Y‘ ? 1 : 0); //u: true; u + n: false add(a + n * (x & 1), b + n * (y ^ 1)); add(b + n * (y & 1), a + n * (x ^ 1)); } if (solve()) for (int i = 1; i <= n; i++) printf("%c", ((ans[i] < 2) ? ((ans[i] == 0) ? ‘N‘ : ‘Y‘) : ‘?‘)); else puts("IMPOSSIBLE"); return 0; }
时间复杂度是$O(n(n+m))$
还有一种做法就是tarjan求scc。
若x和非x在同一个scc里,则无论选x还是非x都会推出矛盾,显然无解。
如果x和非x不再同一个scc中,则选拓扑序较大的一个,这样保证不会推出矛盾。
注意scc缩点的颜色编号是反拓扑序,我们可以利用它,即选scc编号较小的一个。
模板题:【模板】2-SAT 问题
#include <iostream> #include <cstdio> using namespace std; const int N = 1000010 << 1; const int M = 1000010 << 1; struct node{ int pre, to; }edge[M]; int head[N], tot; int n, m; int dfn[N], low[N], col[N], dep, c, stk[N], top, vis[N]; void tarjan(int x, int fa) { dfn[x] = low[x] = ++dep; stk[++top] = x; vis[x] = 1; for (int i = head[x]; i; i = edge[i].pre) { int y = edge[i].to; if (y == fa) continue; if (!dfn[y]) { tarjan(y, x); low[x] = min(low[x], low[y]); } else if (vis[y]) { low[x] = min(low[x], dfn[y]); } } if (dfn[x] == low[x]) { ++c; col[x] = c; vis[x] = 0; while (stk[top] != x) { col[stk[top]] = c; vis[stk[top]] = 0; top--; } top--; } } void add(int u, int v) { edge[++tot] = node{head[u], v}; head[u] = tot; } int read() { int ret = 0, f = 1; char ch = getchar(); while (‘0‘ > ch || ch > ‘9‘) { if (ch == ‘-‘) f = -1; ch = getchar(); } while (‘0‘ <= ch && ch <= ‘9‘) { ret = (ret << 1) + (ret << 3) + ch - ‘0‘; ch = getchar(); } return ret * f; } int main() { n = read(); m = read(); for (int k = 1; k <= m; k++) { int i, j, a, b; i = read(); a = read(); j = read(); b = read(); //i: true; i + n: false add(i + (a & 1) * n, j + (b ^ 1) * n); add(j + (b & 1) * n, i + (a ^ 1) * n); } for (int i = 1; i <= n << 1; i++) { if (!dfn[i]) { tarjan(i, 0); } } bool ans = 1; for (int i = 1; i <= n; i++) { if (col[i] == col[i + n]) { ans = 0; } } if (ans) { puts("POSSIBLE"); for (int i = 1; i <= n; i++) { printf("%d ", col[i] < col[i + n]); } } else { puts("IMPOSSIBLE"); } return 0; }
练习题:
待填坑
以上是关于2-SAT的主要内容,如果未能解决你的问题,请参考以下文章
POJ 3905 Perfect Election(2-sat)