zoukankan      html  css  js  c++  java
  • 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;
    }

    练习题:

    满汉全席(模板题,JS省选竟然出模板!)

    和平委员会(有时模板,POI也放模板)

    [NOI2017]游戏(稍微有点思维难度的模板。)

  • 相关阅读:
    Java初学者笔记四:按行读写文件和输入处理
    Java初学者笔记三:关于字符串和自实现数组常见操作以及异常处理
    Java初学者笔记二:关于类的常见知识点汇总
    python的类继承与派生
    Java初学者笔记一:元类、获取类型、枚举
    Tomcat远程任意代码执行漏洞及其POC(CVE-2017-12617)
    PostgreSQL远程代码执行漏洞(CVE-2018-1058)学习笔记
    python的三个函数(eval、exec、complie)和python版RMI
    关于Memcached反射型DRDoS攻击分析
    python的其他安全隐患
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/12813769.html
Copyright © 2011-2022 走看看