知识点 - 2-SAT
概念
SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。
可以证明,当 k>2 时,k-SAT是NP完全的。因此一般讨论的是 k=2 的情况,即2-SAT问题。
我们通俗的说,就是给你n个变量ai,每个变量能且只能取0/1的值。同时给出若干条件,形式诸如(not)aiopt(not)aj=0/1(not)a_iopt(not) a_j=0/1(not)aiopt(not)aj=0/1,其中optoptopt表示and,or,xorand,or,xorand,or,xor中的一种
而求解2-SAT的解就是求出满足所有限制的一组a
首先我们考虑将2-SAT问题往图论的方向靠,我们发现每个点要么取0,要么取1。因此对于aia_iai,我们建两个点2i−12i−12i−1与2i2i2i分别表示aiaiai取000和111
建边
然后我们考虑建边来表示这些关系,我们令一条有向边的意义:x→yx→yx→y表示如果选择了xxx就必须选yyy
那么我们可以举一些简单的例子来总结下连边的规律(用i′i′i′表示iii的反面):
- i,ji,ji,j必须同时选:选了iii就要选jjj,选jjj就要选iii。故i→j,j→ii→j,j→ii→j,j→i。一般操作即为aia_iai xorxorxor aj=0a_j=0aj=0
- i,ji,ji,j必须且只能 选一个:选了iii就要选j′j'j′,选jjj就要选i′i′i′,选i′i′i′就要选jjj,选j′j′j′就要选iii。故i→j′,j→i′,i′→j,j′→ii→j′,j→i′,i′→j,j′→ii→j′,j→i′,i′→j,j′→i。一般操作即为aia_iai xorxorxor aj=1a_j=1aj=1
- i,ji,ji,j至少 选一个:选个i′i'i′就要选jjj,选j′j'j′就要选iii,aia_iai ororor aj=1a_j = 1aj=1
- i,ji,ji,j至多 选一个:选个iii就要选j′j'j′,选jjj就要选i’i’i’,aia_iai andandand aj=0a_j = 0aj=0
- iii必须选:直接i′→ii′→ii′→i,可以保证无论怎样都选iii。一般操作为给出的ai=1a_i=1ai=1或aia_iai andandand aj=1a_j = 1aj=1
建好图然后就是考虑怎么用图论的方式解决2-SAT了。
建好图然后就是考虑怎么用图论的方式解决2-SAT了。
实现
- 对于每个当前不确定的变量aia_iai,令ai=0a_i=0ai=0然后沿着边DFS访问相连的点。
- 检查如果会导致任意一个jjj与j′j′j′都被选,那么撤销。否则令ai=0a_i=0ai=0
- 否则令ai=1ai=1ai=1,重复2。如果还不行就无解。
- 继续考虑下一个不确定的变量
这样的话正确性显然,由于这里的DFS涉及到全局,因此复杂度是O(n(n+m))O(n(n+m))O(n(n+m))的。
一般情况下已经很优秀了,而且还可以改进:
只需要在DFS之前判断i′i′i′能否走到iii就可以省略撤销标记的过程,所以我们可以bitset优化传递闭包做到O(n3w)OO(n3w)OO(n3w)O预处理,然后就可以O(n+m)O(n+m)O(n+m)的DFS了。
这种做法还可以保证解的字典序,有时不失为一种不错的方法
考虑我们上面的判断有无解的情况,我们想到完全可以借助SCC来判断两个点是否互相到达。
那么我们先缩点,如果iii与i′i′i′在同一SCC里那么显然无解。
否则选择iii与i′i′i′中拓扑序较大的一个就可以得到一组可行解。
不是很常用(主要是一般题目的数据范围都不需要),用来判可行性比较好。
代码
/*
将每个变量拆成两个点2i和2i+1,分别表示xi为假和xi和真
对于每个条件,连两条对称的有向边。
逐一考虑没有赋值的变量,先假定它为假,然后标记结点2i,然后沿着有向边标记所有能标记的结点
如果标记的过程中 发现某个变量对应的两个结点都被标记,则xi为假这个假定不成立,需要改xi为真
然后重新标记
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int maxn=100+10;
struct TwoSAT{
int n;
vector<int>G[2*maxn];
bool mark[maxn*2];
int S[maxn*2],c;
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<G[x].size();i++){
if(!dfs(G[x][i]))return false;
}
return true;
}
void init(int n){
this->n=n;
for(int i=0;i<n*2;i++)G[i].clear();
memset(mark,0,sizeof(mark));
}
void add_clause(int x,int xval,int y,int yval){
x=x*2+xval;
y=y*2+yval;
G[x^1].push_back(y);
G[y^1].push_back(x);
}
bool solve(){
for(int i=0;i<n*2;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;
}
};
int main(){
}