SAT问题总述
SAT是适定性(Satisfiability)问题的简称 。一般形式称为k-适定性问题,简称 k-SAT。 形式化地描述如下:
设
为一个有限个布尔变量所构成的集合,
。取
为
的子集,定义
。求一个
使得给定一组
满足
。
特别地,若
,则称这个问题为
问题。
已经有前人证明了当
时,这是一个
问题,因此我们此处只考虑
时的 k-适应性问题,也称之为 2-SAT (Two-SAT)问题。
2-SAT问题
在2-SAT问题中,变量的限制性只有两种情况:
. 单个的布尔变量
,它没有任何限制。
. 两个变量的相互限制,例如
。
由于单个变量随意取值均符合条件,因此我们只考虑有限制的情况,即第二种情况。
问题分析
为简化问题,我们可以构造一个有向图
,其包含
个顶点,代表
中的
个元素。这样一来我们就可以把问题转化为从图
中选出
个节点,使其满足限制条件。显然,我们不能同时选
与
这样的节点(为方便,用
或者是
都表示图中代表它们的节点,下同)。
那么
在图中代表着什么呢?
我们知道
,这就意味着:如果我们选中
,我们就必须选择
;如果我们选中
,我们就必须选中
。因此对于
,我们可以在图中添加有向边
,
。
解法
老规矩,先想想朴素的解法是什么。显然我们如果钦定一个变量
为
或
,那么与这个变量联通的所有均要取,那么若是在这个过程中我们把一个点的两个状态
与
都取了,这个取法便是不符合条件的,即是我们钦定的
取值就是错的。对于一个变量
如果其的两个取值均不合法,那么这个
问题无解。
此处应该注意的是,这个取值过程是不存在回溯的,也就是说我们当前的
两个取值均不合法的话,不应回溯到先前钦定的那个点,而是直接判断无解。
优化
显然之前那个做法是很不优秀的,我们考虑优化这个做法。对于联通性问题,我们很容易想到缩强联通分量这个算法,这样我们就可以一次性地将一个强联通分量里所有的节点全部取出。就是说如果我们选中强连通分量中的任何一点,那么该强连通分量中的所有其它的顶点也必须被选择。很明显地,如果
和
属于同一个强连通分量,那么产生矛盾,该
问题无解。
如果该问题有解(即未出现矛盾)如果没有产生矛盾,我们就可以把处在同一个强连通分量中的点和边缩成一个点,得到新的有向图
。然后,我们把
中的所有弧反向,得到图
。现在我们观察
。由于已经进行了缩点的操作,因此
中一定不存在环,也就是说,
具有拓扑结构。我们把
中所有顶点置为“未染色”。按照拓扑顺序重复下面的操作:
、选择拓扑序上的第一个“未染色”节点
,将其改为黑色。
、把所有与
矛盾的节点
(如果存在
,且
属于
代表的强连通分量,
属于
代表的强连通分量,那么
和
就是互相矛盾的节点)及其后代全部全部染成白色。
、如果图中还有未染色的节点,则重复
,
操作。
这样一来,图
所被染成黑色的节点所对应的
中的元素集合,则是该
问题的一组解。
证明
接下来我们证明这个算法能对于这个
问题找到一组合法解。
将这个证明分为三步:
命题
、我们得到的解不会同时染黑一组矛盾的节点。
命题
、我们得到的解不会同时染黑
和
。
命题
、我们得到的解不会同时染白
和
。
证明命题 :
首先,假如我们选定了图
中的未染色节点
并将其染黑后,与
矛盾的所有其它节点及其后代均会被染成白色。因此,我们把一个节点
染黑时,任何一个和
矛盾的节点都不会是黑色。
其次,由于我们按照拓扑排序选点,并且把一个顶点染成白色的时候,立刻把它的所有子孙也染成白色。也就是说,如果一个顶点
不可选,那么所有直接或间接满足条件 “ 假如选择
就必须选择
” 的顶点也会被染成白色。这样一来,
中不存在有向边
,其中
为黑色而
为白色。因此我们得到的结论不可能和
对应的条件矛盾。
综合这两条结论,我们就可以证明上面的操作不会选定一组矛盾的节点。证毕。
证明命题 :
首先,对于 和 ,它们在图 中一定属于不同的强连通分量(如果不满足,先前就被判定为无解了),因此在图 中被不同的节点所代表,不妨设为 和 。显然 与 是一对矛盾的节点,既然证明 已经证明了我们的算法不会选择一组矛盾的节点,所以我们不可能同时选中 与 。证毕。
证明命题 :
此处我们采取反证法。
假设我们同时染白了
与
,会出现两种情况。
第一种情况,
单独一个节点作为图
的节点(即图
的强联通分量),那么我们将
染为白色的可能性只有一种,就是我们将
(或者其所在的强联通分量)染成了黑色,这样才会使得与
矛盾的点
染白。
第二种情况,
不作为图
里单独的一个节点,那么必然存在一个节点
,使得
与
处于图
中同一个强联通分量,且
被染为黑色(意即,由于
被染成黑色,导致
这个强联通分量被染白),那么必然存在路径
与路径
。又因为,根据我们的连边方式,
中如果存在一个有向边
,则必然存在有向边
。所以我们有路径
与路径
,即
与
处于同一个强联通分量。那么既然
被染为黑色了,这样
必然也会被染成黑色,与我们同时染白了
与
的假设矛盾,因此不可能出现同时染白
与
的情况。证毕。
这样我们就证明了,对于每一组 ,我们都只会选定 与 中的一个,因此我们证明了这个算法的正确性。
复杂度
首先我们寻找强联通分量的、拓扑排序的时间复杂度都是 ,而染色操作的复杂度是 ,对于每一个限制,我们都会连 条边,因此 是 的,对于每一个变量,我们都会建两个点,因此 是 的。因此对于这个 问题,我们的复杂度是 的 。
简便的写法
我们知道在使用
算法时,强联通分量的编号就是按照图
的拓扑排序的逆序给出的,即是以图
的拓扑排序给出的,所以我们可以直接按照强联通分量的编号来判断
中每个元素的取值。
我们记
为点
所属的强联通分量,则如果
,我们对于元素
取
值,否则取
值。
模板题以及代码
2-sat问题
有
个布尔变量
,编号从
开始。给出一些条件,每个条件用四个整数表示:
,表示要求满足
。
你要判断,是否存在一组布尔变量满足上述所有条件,如果存在,你需要找出一组变量的值。
#define R register
#define LL long long
template<class TT>inline TT Max(R TT a,R TT b){return a<b?b:a;}
template<class TT>inline TT Min(R TT a,R TT b){return a<b?a:b;}
using namespace std;
template<class TT>inline void read(R TT &x){
x=0;R bool f=false;R char c=getchar();
for(;c<48||c>57;c=getchar())f|=(c=='-');
for(;c>47&&c<58;c=getchar())x=(x<<1)+(x<<3)+(c^48);
(f)&&(x=-x);
}
#define maxn 200010
#define maxm 1000010
//Graph
struct Edge{
int to;
Edge *next;
}*head[maxn];
inline void add(R int u,R int v){
static Edge E[maxm],*e=E;
*e=(Edge){v,head[u]};head[u]=e++;
}
//end
//find strongly connected component
stack<int> stk;
int dfs_clo,scc,dfn[maxn],low[maxn],bel[maxn],ins[maxn];
void dfs(R int u){
dfn[u]=low[u]=++dfs_clo;
stk.push(u);ins[u]=1;
R int v;
for(R Edge *i=head[u];i;i=i->next){
if(!dfn[v=i->to]){
dfs(v);
low[u]=Min(low[v],low[u]);
}else if(ins[v]){
low[u]=Min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
scc++;
do{
v=stk.top();
stk.pop();
ins[v]=0;
bel[v]=scc;
}while(v!=u);
}
}
//end
int n,m;
int main(){
read(n);read(m);
for(R int i=1,a,b,c,d;i<=m;++i){
read(a);read(b);read(c);read(d);
add(a+n*(1-b),c+n*d);
add(c+n*(1-u=uvald),a+n*b);
}
for(R int i=0;i<(n<<1);++i){
if(!dfn[i])dfs(i);
}
for(R int i=0;i<n;++i){
if(bel[i]==bel[i+n]){
puts("No");
return 0;
}
}
puts("Yes");
for(R int i=0;i<n;++i){
if(bel[i]<bel[i+n])putchar(48);
else putchar(49);
putchar(' ');
}
return 0;
}