剑锋OI SharpSwordOI

信息学奥赛优秀博客转载&题目翻译&算法总结

关于2-sat的建图方法及解决方案

关于2-sat的建图方法及解决方案

本文非原创,向原创致敬。转发自https://blog.csdn.net/qq_24451605/article/details/47126143

对于2-sat问题的描述

给出一个序列,每个数是一个bool值,给出一些限制关系,得到最终的可行解的问题叫做适应性问题,也就是sat问题,2-sat问题就是给出的限制最多是两两元素之间的限制。
这种适应性问题的解决,同样是能够抽象为我们已知的图论模型的。

2-sat问题的建图方法

1.我们利用一条有向边

(1)如果给出A和B的限制关系,A和B必须一起选,(A and B)||(!A and !B )==true 那么选A必须选B,建边<i,j>和<j,i>还有<i',j'>和<j',i'>

 (2)如果给出A和B的限制关系,选A不能选B,那么(A && !B)||(!A && B )==true,建边<i,j'>和<j,i'>

(3)如果必须选A,那么A==true,建边<i',i>

(4)如果A一定不能选,那么!A==true.建边<i,i'>

这么建图之后,会出现一个有向图,这个有向图会导致一个连通环,导致某个点一旦选取,那么这条链上的所有点都要被选中。如果我们找到一个强连通分量,那么这个强连通分量当中的点,如果选取必须全部选取,不选取的话一定是全部不选取,所以只要满足这个有向图中连通的点不会导致i和i’同时被选取,如果不存在矛盾,那么当前问题就是有解的。但是往往在求解过程中,我们要求的解会要求一些性质,所以提供以下几种解决方案。

2-sat问题的解决方案

1.求字典序最小的解的方法:暴力dfs求解(复杂度O(N*M))

2.判断当前的2-sa问题t是否有解:tarjan强连通缩点,加判断(复杂度O(N+M))

3.求出当前的2-sat问题的任意一组解:tarjan强连通缩点+拓扑排序+构建一组解(复杂度O(N+M))

求字典序最小的解的暴力方法

算法思想:

1.首先定义我们需要用到的数组,mark数组用来标记某个点是否被选取,对于序列中的一个数我们会拆成两个点i和i’,所以我们在利用mark数组进行标记的时候,采用如下这种标记方法:

mark[i<<1]表示i,而mark[i<<1|1]表示i’

一个用来存本次标记过的点的一个队列s

2.枚举每个点,然后判断当前点拆出的两个点是否已经有其中一个被选取,如果有的话,那么继续枚举下一个点,如果没有被标记,那么转到操作3

3.如果某一点拆出的两个点都没有被标记,那么我们先尝试标记第一个点,因为如果标记第一个点会导致一些点必须被标记,所以要进行dfs,然后判断过程中会不会出现矛盾的情况,如果出现了,那么将本次标记的点全部还原,然后就剩下第二个点一种情况,所以我们查看第二种情况,判断会不会出现,,如果出现矛盾,那么问题无解,结束算法如果当前成功标记,那么继续像2那样枚举,直至枚举过所有的点算法结束。

4.因为每次dfs的过程会把所有当前点可达的点都进行标记,所以之后每次标记的过程中,因为已经标记的点,有一个不选的话,那么代表所有的点均不选,且会导致与它同源的那个点一定被选,所以一旦被选中,不能导致出现有解的情况,那么当前情况一定无解,因为每次做的操作只可能会导致图上的点不变或者整体颜色反转,所以只需要让新染色的点两种选择即可,因为得到的结果只有两种,而且同时做反转操作与没做的效果是一样的。

5.因为是按照深搜序做的,所以得到解一定是字典序最小的。

代码如下:

struct TwoSat  
{  
    int n;  
    vector<int> e[MAX<<1];  
    int s[MAX<<1],c;  
    bool mark[MAX<<1];  
    //mark[i<<1]数组等于1,表示点i被选择  
    //mark[i<<1|1]数组等于1,表示点i没有被选择  
    bool dfs ( int x )  
    //用来判断当前的强连通分量当中会不会出现矛盾  
    {  
        //如果需要被选的不能被选那么矛盾  
        if ( mark[x^1] ) return false;  
        //如果需要被选的已经被选,那么当前联通分量一定  
        //不会出现矛盾  
        if ( mark[x] ) return true;  
        //如果当前点需要被选,那么选上它,并且标记  
        mark[x] = true;  
        //当前的强连通分量加上这个点  
        s[c++] = x;  
        //找到与当前点相连点,判断他们的状态  
        for ( int i = 0 ; i <e[x].size() ; i++ )  
            if ( !dfs( e[x][i] ))  
                return false;  
        return true;  
    }  

    void init ( int n )  
    {  
        this->n = n;  
        for ( int i = 0 ; i < 2*n ; i++ )  
            e[i].clear();  
        memset ( mark , 0 , sizeof ( mark ));  
    }  

    void add ( int x , int y )  
    {  
        e[x].push_back ( y^1 ); //建边操作考虑实际情况修改
        e[y].push_back ( x^1 );  
    }  

    bool solve ( )  
    {  
        for ( int i = 0 ; i < 2*n ; i += 2 )  
            if ( !mark[i] && !mark[i+1] )  
            {  
                c = 0;  
                if ( !dfs(i) )  
                {  
                    //如果矛盾,那么这个强连通分量里的点都不能  
                    //选取  
                    while ( c > 0 ) mark[s[--c]]= false;  
                    if ( !dfs(i+1) )  return false;  
                }  
            }  
        return true;  
    }  

利用强连通缩点判断2-sat问题是否有解

算法思想:

1.利用强连通缩点得到一个DAG(有向无环图);

2.然后对于每个强连通分量当中,所有点都是选就一起选,不选就一起不选的,所以如果i和i’同时存在一个强连通分量里,就一定无解

3.如果强连通分量内部不出现矛盾,那么剩下的就是这个有向无环图,因为有向无环图,可以进行拓扑排序,所以只需要交替的染不同的颜色,就能够得到一个解,所以只要强连通分量内部不出现矛盾,那么久一定有解

主体代码如下:

 for ( int i = 0 ; i < 2*n ; i++ )  
        if ( !mark[i] ) tarjan ( i );  

    for ( int i = 0 ; i < n ; i++ )  
        if ( belong[i<<1] == belong[i<<1|1] )  
            return false;  
    return true;  

tarjan强连通缩点再补一发:

void tarjan ( int u )  
{  
    dfn[u] = low[u] = ++times;  
    mark[u] = 1;  
    s.push ( u );  
    int len = e[u].size();  
    for ( int i= 0 ; i < len ; i++ )  
    {  
        int v = e[u][i];  
        if ( !mark[v] )  
        {  
            tarjan ( v );  
            low[u] = min ( low[u] , low[v] );  
        }  
        if ( mark[v] == 1 )  
            low[u] = min ( low[u] , dfn[v] );  
    }  
    if ( dfn[u] == low[u] )  
    {  
        int temp;  
        do  
        {  
            temp = s.top();  
            belong[temp] = cnt;  
            mark[temp] = 2;  
            s.pop();  
        }while ( temp != u );  
        cnt++;  
    }  
}  

按照拓扑序求得任意一组解

1.首先依旧要进行强连通缩点,我们得到一个DAG

2.然后我们要得到新得到的图中的矛盾关系,也就是i和i’所在的强连通分量是矛盾的。

3.然后我们对DAG进行染色,在拓扑排序的过程中进行染色,如果某个点没有染色,那么染为1,并且将与他矛盾的点染为2,因为矛盾关系是两两之间的,所以不会与其他点出现矛盾。

4.那么在拓扑排序结束之后就对所有点进行完染色了。拓扑只是在有向无环图中的一种很好的遍历方式

代码如下:

void topsort ( )  
{  
    int i,j;  
    queue<int> q;  
    for ( int i = 1 ; i < t ; i++ )  
    {  
        if (!in[i])  
            q.push ( i );  
    }  
    while (!q.empty())  
    {  
        int u = q.front();  
        q.pop();  
        if ( !col[u] )  
        {  
            col[u] = 1;  
            col[conflict[u]] = 2;  
        }  
        for ( int i = 0 ; i < g[u].size(); i++ )  
        {  
            int v = g[u][i];  
            in[v]--;  
            if ( !in[v] ) q.push ( v );  
        }  
    }  
}  
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

关于2-sat的建图方法及解决方案

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭