zoukankan      html  css  js  c++  java
  • 2-SAT问题介绍求解 + 模板题P4782

    (点击此处查看原题)

    什么是2-SAT问题

    sat 即 Satisfiability,意思为可满足,那么2-SAT表示一些布尔变量只能取true或者false,而某两个变量之间的值存在一定的关系(如:只要a为真,b一定为假;如果a为假,b也为假),我们需要在满足所有这样的关系的情况下,求出每个变量的赋值,如果不存在,就是无解。

    举个栗子

    某一天,PC,YD,HL在讨论两个问题:1)winter的温度是否低于0度,2)bamboo的高度是否大于10m,众所周知,这三位大佬很强大,所以他们对于这两个问题的判断至少有一个是正确的。此时给出三人对这两个问题的判断,求出这两个问题的正确答案;如果不存在正确答案,那么可能是某个人犯糊涂了,因此我们无法得到答案,输出无解。

    我们将两个问题符号化为,a:winter的温度低于0度, b :bamboo的高度大于10m,将三人的观点及其符号化表示如下:

    1)PC认为:winter的温度低于0度,bamboo的高度大于10m,a∨ b

    2)YD认为:wintet的温度不低于0度,bamboo的高度大于10m,¬a∨ b

    3)HL认为:winter的温度不低于0度,bamboo的高度不大于10m,¬a∨ ¬b

     那么a,b的取值需要满足:(a∨ b) ∧ ( ¬a∨ b) ∧ (¬a∨ ¬b),我们如何求出满足这个式子的a,b的取值这类问题就是2-SAT问题。

    将2-SAT问题转为图论问题求解

    我们可以将每个变量x的两个状态分别用两个点表示,记编号为i的点表示这个变量取真,i+n的点表示这个变量取假

    将两个变量之间的关系用边表示,如:只要a为真,b一定为假,将此命题符号化得到¬a¬b,这个式子也可以写成:a → ¬b ∧ b → ¬a,表示:a为真,则b必为假 ,和b为真,则a必为假,这样我们就发现 a 和 ¬b 可以由a推出¬b, 可以由b推出¬a,在图中,我们构建这样的边表示他们的关系:由 a 向  ¬b 建一条单向边,再由 b 向 ¬a建一条单向边,总结以下将命题转化为建图的规律:

    1)¬a¬b   ----> a → ¬b ∧ b → ¬a 

    2)ab   ----> ¬a → b ∧ ¬b → a

    3)¬ab   ----> a → b ∧ ¬b → ¬a 

    4)a∨¬b   ----> ¬a → ¬b ∧ b → a 

    (x→y可以视作由x向y建一条单向边)

    然后,我们在这个图中求强连通分量,联想到我们图中边代表的关系:由边的起点可以推出终点,所以同一强连通分量中的点真值一致,所以我们很容易想到如果 a 和 ¬a在同一强连通分量中,这说明a和¬a真值相同,这显然是不正确的,即无解;如果不存在变量x使得x和¬x在同一强连通分量中,就说明有解。

    那么有解的情况下,我们如何得到每个变量的值呢?只要x所在强连通分量的拓扑序比¬x所在强连通分量的拓扑序靠后,则x为真,否则为假,而我们用tarjan算法求强连通分量的时候,对于每个强连通分量的标记是逆拓扑序的,所以 node[x] < node[¬x] 时,x取真,否则为假,这样一来,我们对每个变量进行判断并输出对应的值即可

    代码区

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<string>
    #include<fstream>
    #include<vector>
    #include<stack>
    #include <map>
    #include <iomanip>
    
    #define bug cout << "**********" << endl
    #define show(x, y) cout<<"["<<x<<","<<y<<"] "
    #define LOCAL = 1;
    using namespace std;
    typedef long long ll;
    const ll inf = 2e9 + 10;
    const ll mod = 1e9 + 7;
    const int Max = 2e6 + 10;
    const int Max2 = 3e2 + 10;
    
    struct Egde
    {
        int to, next;
    } edge[Max];
    
    int n, m;
    int head[Max], tot;
    int dfn[Max], low[Max],time_clock;
    int line[Max],now;
    int node[Max],sccCnt;
    
    void init()
    {
        memset(head, -1, sizeof(head));
        tot = 0;
        memset(dfn,0,sizeof(dfn));
        time_clock = 0;
        now = 0;
        memset(node,0,sizeof(node));
        sccCnt = 0;
    }
    
    void add(int u, int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        head[u] = tot++;
    }
    
    void tarjan(int u)
    {
        dfn[u] = low[u] = ++time_clock;
        line[++now] = u;
        for(int i = head[u] ; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                low[u] = min(low[u],low[v]);
            }
            else if(!node[v])
            {
                low[u] = min(low[u],dfn[v]);
            }
        }
        if(dfn[u] == low[u])
        {
            sccCnt++;
            while(line[now] != u)
                node[line[now--]] = sccCnt;
            node[line[now--]] = sccCnt;
        }
    }
    
    int main()
    {
    #ifdef LOCAL
    //        freopen("input.txt", "r", stdin);
    //        freopen("output.txt", "w", stdout);
    #endif
        while (scanf("%d%d", &n, &m) != EOF)
        {
            init();
            for (int i = 1, a, va, b, vb; i <= m; i++)
            {
                scanf("%d%d%d%d", &a, &va, &b, &vb);
                add(a + n * (va & 1), b + n * (vb ^ 1));
                add(b + n * (vb & 1), a + n * (va ^ 1));       //1~n表示1,n+1,2*n表示0
            }
            for(int i = 1;i <= (n << 1) ;i ++)
            {
                if(!dfn[i])
                    tarjan(i);
            }
            bool ok = true;
            for(int i = 1;i <= n ;i ++)
            {
                if(node[i] == node[i+n])
                {
                    ok = false;
                    break;
                }
            }
            if(!ok)
            {
                printf("IMPOSSIBLE
    ");
            }
            else
            {
                printf("POSSIBLE
    ");
                for(int i = 1;i < n ;i ++)
                {
                    printf("%d ",node[i] < node[i+n]);
                }
                printf("%d
    ",node[n] < node[n<<1]);
            }
        }
        return 0;
    }
    View Code
  • 相关阅读:
    第三讲:增长全局观
    搭建安卓环境
    ~~
    天气阴
    天气晴
    Spark性能优化指南——高级篇
    Ceph Jewel 10.2.3 环境部署
    《你只是看起来很努力》--读书笔记
    博客园基础环境配置
    Spark 1.3.0 单机安装
  • 原文地址:https://www.cnblogs.com/winter-bamboo/p/11402634.html
Copyright © 2011-2022 走看看