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的主要内容,如果未能解决你的问题,请参考以下文章

模板 2-sat

POJ 3905 Perfect Election(2-sat)

BZOJ1823: [JSOI2010]满汉全席(2-sat)

[luogu4782]模板2-SAT 问题

POJ 2296 Map Labeler(2-sat)

BZOJ1823 [JSOI2010]满汉全席 2-sat