• 2-SAT问题


    传送门

    什么是2-SAT问题呢?

    我们先说一下SAT问题。给定一个布尔方程,判断是否存在一组布尔变量的取值方案,使得整个方程式的值为真,这种问题被称为布尔方程的可满足性问题(SAT)。SAT问题被证明是NP完全的,当k  > 2的时候我们无法在多项式时间之内求解,但是对于一些特殊的SAT(比如2-SAT)我们可以有效求解。

    注:因为笔者不会打与,或,非(否定)的数学符号,所以下文中,“与”使用&&代替,“或”使用||代替,“非”使用!代替。

    布尔变量只有两个取值,0和1.

    我们称下面这种布尔方程为合取范式:

    (a || b || c || ……)^ (d || e || f || g ……) ^ ……

    其中a,b等等称为文字,是一个布尔变量或其否定。使用 || 连接的部分称为子句。如果合取范式的每个子句中的文字个数不超过两个,那么对应的SAT问题称为2-SAT问题。

    解法:

    首先,因为子句都是由不超过两个文字组成的,用|| 表示的关系不是很好理解,我们其实要求的是满足所有条件的一组解,也就是说其实求的是交集(&&),所以我们首先要把“a || b”的形式转换为使用&&表示。

    它的等价形式是(!a => b)&& (!b => a),其中=>是蕴含,我们可以把它理解为“推出”的意思。

    它的意思是,如果a为真,那么b必然为真,否则如果a为假,那么b可以为真或者为假。

    为什么呢?我们可以用现实中推理事情的过程类比一下。如果我们已知A事件,我们用A能够推出B,那么如果A是真的,那么B必然也是真的。如果A是假的话,说明你使用了一种错误的方法去推B,这样的话B的正确性就是不能确定的,所以真或者假都可以。

    于是我们就可以将一个合取范式转化为全部由“&&”连接的形式。

    对于上文的转换可以自行推一下,只要不是两个0都是成立的。

    这是对于每个子句都有两个文字的情况,但是如果只有一个文字呢?

    这里首先要说一条重要的结论:如果由!a能推出a,那么a必然为真。

    我们假设一下,首先假设a为假,那么!a就是真,然后由于推出的性质,我们又能推出a为真,这与假设a为假是矛盾的。而假设a为真的话,他就是成立的。所以结论是成立的。

    既然如此,对于一个单独的文字组成的合取范式,比如(a),那就把它的否定连向它,(!a)也是一样的。

    然后对于每一个子句我们是需要建两条边的。因为,一个子句是两者互相限制的,上文(a || b)的限制比较宽松,但是如果我们建立一个小型的布尔方程,我们就能看出来如果少建一条边对于结果的影响。

    比如:(b && !a && (a || !b))

    对于最后一个合取范式,如果你只建一条边的话,他就是合法的,建立两条就是不合法的。因为如果只建一条的话,那么我们就少了一种限制,所以答案就会被影响。

    不过对于一个只有一个文字的情况,我们只需要建一条边。因为,仔细观察的话两边是互相对应的。但是只有一个文字的时候,它对应的反向边仍然是他自己,所以只用建立一条即可。

    这样的话,我们把合取范式转化为全部由&&连接的形式,之后我们以=>建立有向边,那么我们就有了一张有向图。因为推出是具有传递性的,所以如果在这个有向图中a能到达b,那就说明a为真的时候b一定为真。所以,在一个图中同一个强连通分量所含的所有变量的布尔值均相同。(要么全部为真要么全部为假,否则就违反了上文)

    之后我们就很容易看出,如果对于某个布尔变量x,x和!x同时存在于一个强连通分量中,那么这个布尔方程就是无解的,否则的话,我们只要先缩点,使之成为一个DAG,之后我们求出它的一个拓扑序,用上面重要的一条结论:如果由!a能推出a,那么a必然为真。来解决问题。只要在缩点之后x所在的强连通分量的拓扑序在!x所在的强连通分量的拓扑序之后,那么x即为真。

    (拓扑序在前面能推出拓扑序在后面的强连通分量)

    不过因为tarjan求强连通分量的时候,强连通分量的编号实际是与拓扑序相反的,所以我们不用真的缩点,直接比较一下两个点所在的强连通分量的编号即可。

    所以我们就知道了2-SAT问题的一般解法:

    1.将给定的合取范式变为&&连接的表示形式。

    2.以=>为关系建图。

    3.跑一遍tarjan,求出所有的布尔变量所在的强连通分量的编号。

    4.首先比较每个变量所在强连通分量编号,如果自己和自己的否定在一个里面就不合法。

    5.否则的话存在一组解,我们只要再比较一下每个变量自己和自己的否定的拓扑序,对其赋值即可。

    我们来看一下代码。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<set>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    typedef long long ll;
    const int M = 1000005;
    const int INF = 1000000009;
    
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
        if(ch == '-') op = -1;
        ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
        }
        return ans * op;
    }
    
    struct edge
    {
        int next,to,from;
    }e[M<<1];
    
    int n,m,a,b,x,y,dfn[M<<1],low[M<<1],scc[M<<1],ecnt,idx,cnt,stack[M<<1],top,head[M<<1];
    bool vis[M<<1];
    
    void add(int x,int y)
    {
        e[++ecnt].to = y;
        e[ecnt].from = x;
        e[ecnt].next = head[x];
        head[x] = ecnt;
    }
    
    int rev(int x)
    {
        return x > n ? x - n : x + n;
    }
    
    void tarjan(int x)
    {
        low[x] = dfn[x] = ++idx;
        vis[x] = 1,stack[++top] = x;
        for(int i = head[x];i;i = e[i].next)
        {
        if(!dfn[e[i].to]) tarjan(e[i].to),low[x] = min(low[x],low[e[i].to]);
        else if(vis[e[i].to]) low[x] = min(low[x],dfn[e[i].to]);
        }
        if(dfn[x] == low[x])
        {
        int p;
        cnt++;
        while(p = stack[top--])
        {
            scc[p] = cnt,vis[p] = 0;
            if(p == x) break;
        }
        }
    }
    
    int main()
    {
        n = read(),m = read();
        rep(i,1,m)
        {
        a = read(),x = read(),b = read(),y = read();
        if(x) a += n;
        if(y) b += n;
        add(rev(a),b),add(rev(b),a);
        }
        rep(i,1,n<<1) if(!dfn[i]) tarjan(i);
        rep(i,1,n)
        {
        if(scc[i] == scc[i+n])
        {
            printf("IMPOSSIBLE
    ");
            return 0;
        }
        }
        printf("POSSIBLE
    ");
        rep(i,1,n) (scc[i] < scc[i+n]) ? printf("0 ") : printf("1 ");enter;
        return 0;
    }
  • 相关阅读:
    git log中文乱码问题
    局域网映射公网IP
    Android Studio 的一些配置
    Android Studio的安装
    adb的安装
    python的安装
    CentOS 7 上部署 java web 项目
    SQL——SQL语句总结(8)
    SQL——SQL语句总结(7)
    SQL——SQL语句总结(6)
  • 原文地址:https://www.cnblogs.com/captain1/p/9760503.html
Copyright © 2020-2023  润新知