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