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

来源:互联网 发布:移动网络怎么样 编辑:程序博客网 时间:2024/06/05 03:59

-------------------------------------------------对于2-sat问题的描述-------------------------------------------------

给出一个序列,每个数是一个bool值,给出一些限制关系,得到最终的可行解的问题叫做适应性问题,也就是sat问题,2-sat问题就是给出的限制最多是两两元素之间的限制。

这种适应性问题的解决,同样是能够抽象为我们已知的图论模型的。

--------------------------------------------------2-sat问题的建图方法--------------------------------------------------

1.我们利用一条有向边<i,j>,来表示选i的情况下,一定要选j;

2.用i表示某个点是true,那么i'表示某个点是false

3.因为限制的两两之间的关系,所以我们可以通过逻辑关系来建边:

          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 );          }      }  }  


0 0
原创粉丝点击