Night的算法杂谈-2SAT问题

SAT问题总述

SAT是适定性(Satisfiability)问题的简称 。一般形式称为k-适定性问题,简称 k-SAT。 形式化地描述如下:
A={a1,a2,,an} A = { a 1 , a 2 , ⋯ , a n } 为一个有限个布尔变量所构成的集合, A^={a1,a2,,an,¬a1,¬a2,,¬an} A ^ = { a 1 , a 2 , ⋯ , a n , ¬ a 1 , ¬ a 2 , ⋯ , ¬ a n } 。取 S S A^ 的子集,定义 S=s1s2sk (siS) ∨ S = s 1 ∨ s 2 ∨ ⋯ ∨ s k   ( s i ∈ S ) 。求一个 A A 使得给定一组 S1,S2,,Sm 满足 (S1)(S2)(Sm)=1 ( ∨ S 1 ) ∨ ( ∨ S 2 ) ∨ ⋯ ∨ ( ∨ S m ) = 1
特别地,若 max{|Si|  i[1,m]}k max { | S i |   ∣   i ∈ [ 1 , m ] } ≤ k ,则称这个问题为 k-sat k-sat 问题。
已经有前人证明了当 k3 k ≥ 3 时,这是一个 NP-complete NP-complete 问题,因此我们此处只考虑 k=2 k = 2 时的 k-适应性问题,也称之为 2-SAT (Two-SAT)问题。


2-SAT问题

在2-SAT问题中,变量的限制性只有两种情况:
1 1 . 单个的布尔变量 x,它没有任何限制。
2 2 . 两个变量的相互限制,例如 xy=1 ( x,yA^ )
由于单个变量随意取值均符合条件,因此我们只考虑有限制的情况,即第二种情况。

问题分析

为简化问题,我们可以构造一个有向图 G G ,其包含 2n 个顶点,代表 A^ A ^ 中的 2n 2 n 个元素。这样一来我们就可以把问题转化为从图 G G 中选出 n 个节点,使其满足限制条件。显然,我们不能同时选 x x ¬x 这样的节点(为方便,用 x x 或者是 ¬x 都表示图中代表它们的节点,下同)。
那么 xy=1 x ∨ y = 1 在图中代表着什么呢?
我们知道 xy=¬ (¬x¬y) x ∨ y = ¬   ( ¬ x ∧ ¬ y ) ,这就意味着:如果我们选中 ¬x ¬ x ,我们就必须选择 y y ;如果我们选中 ¬y ,我们就必须选中 x x 。因此对于 Si=xy ,我们可以在图中添加有向边 (¬x,y) ( ¬ x , y ) (¬y,x) ( ¬ y , x )

解法

老规矩,先想想朴素的解法是什么。显然我们如果钦定一个变量 x x 0 1 1 ,那么与这个变量联通的所有均要取,那么若是在这个过程中我们把一个点的两个状态 y ¬y ¬ y 都取了,这个取法便是不符合条件的,即是我们钦定的 x x 取值就是错的。对于一个变量 x 如果其的两个取值均不合法,那么这个 2SAT 2 − S A T 问题无解。
此处应该注意的是,这个取值过程是不存在回溯的,也就是说我们当前的 x x 两个取值均不合法的话,不应回溯到先前钦定的那个点,而是直接判断无解。

优化

显然之前那个做法是很不优秀的,我们考虑优化这个做法。对于联通性问题,我们很容易想到缩强联通分量这个算法,这样我们就可以一次性地将一个强联通分量里所有的节点全部取出。就是说如果我们选中强连通分量中的任何一点,那么该强连通分量中的所有其它的顶点也必须被选择。很明显地,如果 x ¬x ¬ x 属于同一个强连通分量,那么产生矛盾,该 2SAT 2 − S A T 问题无解。
如果该问题有解(即未出现矛盾)如果没有产生矛盾,我们就可以把处在同一个强连通分量中的点和边缩成一个点,得到新的有向图 G G ′ 。然后,我们把 G' G ′ 中的所有弧反向,得到图 G'' G ′ ′ 。现在我们观察 G'' G ′ ′ 。由于已经进行了缩点的操作,因此 G'' G ′ ′ 中一定不存在环,也就是说, G'' G ′ ′ 具有拓扑结构。我们把 G'' G ′ ′ 中所有顶点置为“未染色”。按照拓扑顺序重复下面的操作:
1 1 、选择拓扑序上的第一个“未染色”节点 x,将其改为黑色。
2 2 、把所有与 x 矛盾的节点 y y (如果存在 ai,¬aiA^,且 bi b i 属于 x x 代表的强连通分量,¬bi 属于 y y 代表的强连通分量,那么 x y y 就是互相矛盾的节点)及其后代全部全部染成白色。
3、如果图中还有未染色的节点,则重复 1 1 2 操作。
这样一来,图 G′′ G ″ 所被染成黑色的节点所对应的 A^ A ^ 中的元素集合,则是该 2SAT 2 − S A T 问题的一组解。

证明

接下来我们证明这个算法能对于这个 2SAT 2 − S A T 问题找到一组合法解。
将这个证明分为三步:
命题 1 1 、我们得到的解不会同时染黑一组矛盾的节点。
命题2 、我们得到的解不会同时染黑 ai a i ¬ai ¬ a i
命题 3 3 、我们得到的解不会同时染白 ai ¬ai ¬ a i

证明命题 1 1

首先,假如我们选定了图 G 中的未染色节点 x x 并将其染黑后,与 x 矛盾的所有其它节点及其后代均会被染成白色。因此,我们把一个节点 x x 染黑时,任何一个和 x 矛盾的节点都不会是黑色。
其次,由于我们按照拓扑排序选点,并且把一个顶点染成白色的时候,立刻把它的所有子孙也染成白色。也就是说,如果一个顶点 x x 不可选,那么所有直接或间接满足条件 “ 假如选择 y 就必须选择 x x ” 的顶点也会被染成白色。这样一来,G 中不存在有向边 (x,y) ( x , y ) ,其中 x x 为黑色而 y 为白色。因此我们得到的结论不可能和 Si S i 对应的条件矛盾。
综合这两条结论,我们就可以证明上面的操作不会选定一组矛盾的节点。证毕。

证明命题 2 2

首先,对于 ai ¬ai ¬ a i ,它们在图 G G 中一定属于不同的强连通分量(如果不满足,先前就被判定为无解了),因此在图 G 中被不同的节点所代表,不妨设为 x x y。显然 x x y 是一对矛盾的节点,既然证明 1 1 已经证明了我们的算法不会选择一组矛盾的节点,所以我们不可能同时选中 x y y 。证毕。

证明命题 3

此处我们采取反证法。
假设我们同时染白了 x x ¬x,会出现两种情况。
第一种情况, x x 单独一个节点作为图 G 的节点(即图 G G 的强联通分量),那么我们将 x 染为白色的可能性只有一种,就是我们将 ¬x ¬ x (或者其所在的强联通分量)染成了黑色,这样才会使得与 ¬x ¬ x 矛盾的点 x x 染白。
第二种情况,x 不作为图 G G ′ 里单独的一个节点,那么必然存在一个节点 y y ,使得 y x x 处于图 G 中同一个强联通分量,且 ¬y ¬ y 被染为黑色(意即,由于 ¬y ¬ y 被染成黑色,导致 y y 这个强联通分量被染白),那么必然存在路径(x,y) 与路径 (y,x) ( y , x ) 。又因为,根据我们的连边方式, G G 中如果存在一个有向边 (a,b),则必然存在有向边 (¬a,¬b) ( ¬ a , ¬ b ) 。所以我们有路径 (¬y,¬x) ( ¬ y , ¬ x ) 与路径 (¬x,¬y) ( ¬ x , ¬ y ) ,即 ¬x ¬ x ¬y ¬ y 处于同一个强联通分量。那么既然 ¬y ¬ y 被染为黑色了,这样 ¬x ¬ x 必然也会被染成黑色,与我们同时染白了 x x ¬x 的假设矛盾,因此不可能出现同时染白 x x ¬x 的情况。证毕。

这样我们就证明了,对于每一组 ai,¬ai a i , ¬ a i ,我们都只会选定 ai a i ¬ai ¬ a i 中的一个,因此我们证明了这个算法的正确性。

复杂度

首先我们寻找强联通分量的、拓扑排序的时间复杂度都是 O(v+e) O ( v + e ) ,而染色操作的复杂度是 O(v+e) O ( v + e ) ,对于每一个限制,我们都会连 2 2 条边,因此 e O(m) O ( m ) 的,对于每一个变量,我们都会建两个点,因此 v v O(n) 的。因此对于这个 2SAT 2 − S A T 问题,我们的复杂度是 O(n+m) O ( n + m ) 的 。

简便的写法

我们知道在使用 Tarjan T a r j a n 算法时,强联通分量的编号就是按照图 G G ′ 的拓扑排序的逆序给出的,即是以图 G′′ G ″ 的拓扑排序给出的,所以我们可以直接按照强联通分量的编号来判断 A A 中每个元素的取值。
我们记 beli 为点 i i 所属的强联通分量,则如果 beli<bel¬i,我们对于元素 ai a i 0 0 值,否则取 1 值。


模板题以及代码
2-sat问题

n n 个布尔变量 xi,编号从 0 0 开始。给出一些条件,每个条件用四个整数表示:u,uval,v,vval,表示要求满足 (xu=uval)(xv=vval) ( x u = u v a l ) ∨ ( x v = v v a l )
你要判断,是否存在一组布尔变量满足上述所有条件,如果存在,你需要找出一组变量的值。

#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-d),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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值