2-SAT问题详解

2-SAT问题详解

前言

S A T SAT SAT是适定性(Satisfiability)问题的简称,适定性问题指的是有 N N N个变量,每个变量只有两种可能的取值。再给定 M M M个条件,每个条件都是对这 k k k个变量取值的限制,问是否存在对这 N N N个变量的合法赋值使得 M M M个条件均得到满足。这种问题被称为 K − S A T K-SAT KSAT问题,当 K > 2 K>2 K>2时, K − S A T K-SAT KSAT问题为 N P NP NP完全问题。

某科上是这样介绍 N P NP NP完全问题的:“NP完全问题(NP-Cm问题),是世界七大数学难题之一。 NP的英文全称是Non-deterministic Polynomial的问题,即多项式复杂程度的非确定性问题。简单的写法是 NP=P?,问题就在这个问号上,到底是NP等于P,还是NP不等于P。”

通俗地来说,有些计算问题是确定性的,比如加减乘除之类,你只要按照公式推导,按部就班一步步来,就可以得到结果。但是,有些问题是无法按部就班直接地计算出来的。一般这种无法按部就班计算出来的问题,只能通过穷举法等暴力的方法来解决。

所以,我们不去学习 K − S A T K-SAT KSAT问题,我们来学习 k = 2 k=2 k=2时的 2 − S A T 2-SAT 2SAT问题。

由于 2 − s a t 2-sat 2sat问题是只针对两个元素进行限制

2-SAT问题模板

【模板】2-SAT问题

有n个布尔变量 x 1 … x n x_1\dots x_n x1xn,另外有 m m m个条件,每个条件的形式是「 x i x_i xitrue / false x j x_j xjtrue / false」。比如 「 x 1 x_1 x1 为真或 x 3 x_3 x3 为假」、「 x 7 x_7 x7 为假或$ x_2$ 为假」。

2 − S A T 2-SAT 2SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

在这里插入图片描述

分析:

1.建图

我们假设 i i i点表示为真, i + n i+n i+n点表示为假,这样就可以建成一个点数为 2 n 2n 2n的有向图

只有关系明确才能建图,因为我们通过明确的关系,然后去推出有没有合适的赋值使得这些关系成立。

在上述样例中1 1 3 0表示取1和不取3的情况至少满足一个。我们将取1的情况定义为 x x x,不取1的情况则定义为 ¬ x \neg x ¬x。同理,将取3的情况定义成 y y y,不取3的情况定义为 ¬ y \neg y ¬y。那么就有两种情况一定成立,如果不取1的话必定不取3,取3的情况下必定取1。也就是不满足 x x x的情况下必定满足 y y y,不满足 y y y的情况下必定满足 x x x。设边 < x , y > <x,y> <x,y>表示若选了 x x x则必须选择 y y y。所以有 < ¬ x , y > <\neg x,y> <¬x,y> < ¬ y , x > <\neg y,x> <¬y,x>

除了上述关系外,还有其他的常见关系为

  • 选了 x x x必须选$y : : x → y , ¬ y → ¬ x$
  • x , y x,y x,y至多选一个: x → ¬ y , y → ¬ x x → ¬ y , y → ¬ x x¬y,y¬x
  • x , y x,y x,y至少选一个: ¬ x → y , ¬ y → x ¬ x → y , ¬ y → x ¬xy,¬yx
  • x , y x , y x,y恰好选一个:$¬ x → y , ¬ y → x , x → ¬ y , y → ¬ x $
  • x , y x , y x,y要么都选,要么都不选:$x → y , y → x , ¬ x → ¬ y , ¬ y → ¬ x $
  • x x x 不能选:$¬ x → x $
  • $x 必 须 选 : 必须选: x → ¬ x$

2.求连通分量,判断是否有解

通过Tarjan进行求连通分量后,如果 x x x ¬ x \neg x ¬x在同一个连通分量中,这就表明这个点同时存在必须取和不能取这两种状态,显然是不可能的。所以建边后,如果发现 ¬ x \neg x ¬x x x x在同一个连通分量中,那么就无法通过赋值去满足要求。

3.如何给变量赋值(参考了暗い之殇博客)

知道如何判断是否有解后,就可以继续往下进行。因为bool方程组有很多组解,如何判断到底那一组或者哪几组解符合我们的需求?

仅需要每一个集合选取强连通分量小的即可。

img

我们发现,从 x 1 x_1 x1的 false出发会走到 x 1 x_1 x1 的 true,也就是说 x 1 x_1 x1 现在只能等于 true了;同理从 x 2 x_2 x2 的 true 出发能走到$ x_2$的 flase,说明 x 2 x_2 x2只能等于 false;

解释一下吧:

如果我们不将 x 1 x_1 x1 赋值为 true,而是赋值为 false,那会发生什么呢?

由$ x_1$ 的 false是能明确推出 x 2 x_2 x2 是为 true 的,但是又有 x 2 x_2 x2 的 true 能明确推出 x 1 x_1 x1 为 true,这与刚刚我们将$ x_1$赋值为 false 是相矛盾的;

有了这个性质,就说明在有解的情况下,一个变量的两个取值是有前后推导关系的,也就是一个取值直接或间接的指向了另一个取值;

而为什么是选取连通分量编号小的那一个即可呢?因为tarjan算法在计算强连通分量时,借助深度优先搜索以及栈,拓扑序越大的点在一棵树上是越靠近叶节点的,然后越靠近叶节点的那些节点在 Tarjan的时候是越早被缩点的,所以拓扑序越大的点其所在强联通分量编号越小。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
long long read()
{
    char ch=getchar();
    long long a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<1)+(a<<3)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
const int N=4e6+5;
int n,m,a,b,x,y,tim,top,edge_sum,scc_sum;
int dfn[N],low[N],st[N],vis[N],scc[N],head[N];
struct node
{
    int to,next;
}A[N];
void add(int from,int to)
{
    edge_sum++;
    A[edge_sum].next=head[from];
    A[edge_sum].to=to;
    head[from]=edge_sum;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++tim;
    st[++top]=u;
    vis[u]=1;
    for(int i=head[u];i;i=A[i].next)
    {
        int v=A[i].to;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]) low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        scc_sum++;
        while(st[top]!=u)
        {
            scc[st[top]]=scc_sum;
            vis[st[top]]=0;
            top--;
        }
        scc[st[top]]=scc_sum;
        vis[st[top]]=0;
        top--;
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        a=read();x=read();  //第a个数为x或第b个数为y 
        b=read();y=read();
        if(x==0&&y==0)      //"如果第a个数为0或第b个数为0"至少满足其一 
        {
            add(a+n,b);     //a=1则b=0 
            add(b+n,a);     //b=1则a=0 
        }
        if(x==0&&y==1)      //"如果第a个数为0或第b个数为1"至少满足其一 
        {
            add(a+n,b+n);   //a=1则b=1 
            add(b,a);       //b=0则a=0 
        }
        if(x==1&&y==0)      //"如果第a个数为1或第b个数为0"至少满足其一
        {
            add(a,b);       //a=0则b=0 
            add(b+n,a+n);   //b=1则a=1 
        }
        if(x==1&&y==1)      //"如果第a个数为1或第b个数为1"至少满足其一
        {
            add(a,b+n);     //a=0则b=1 
            add(b,a+n);     //b=0则a=1 
        }
    }
    for(int i=1;i<=2*n;i++) //对每个变量的每种取值进行tarjan 
    {
        if(!dfn[i]) tarjan(i);
    }
    for(int i=1;i<=n;i++)   //判断无解的情况 
    { 
        if(scc[i]==scc[i+n]) //同一变量的两种取值在同一强联通分量里,说明无解 
        {
            printf("IMPOSSIBLE\n");
            return 0;
        }
    }
    printf("POSSIBLE\n");   //否则就是有解 
    for(int i=1;i<=n;i++)
    {
        if(scc[i]>scc[i+n]) printf("1 ");  //强联通分量编号越小 -> 拓扑序越大 -> 越优 
        else printf("0 ");
    }
    return 0;
}
2-SAT问题情景

P5782 [POI2001]和平委员会

某国有 n n n个党派,每个党派在会议中恰好有2个代表,现在要成立和平委员会,该会满足:每个党派在和平委员会中有且只有一个代表。如果两个代表不和则他们都不能属于委员会。

每个党在议会中有 2 2 2 个代表。代表从 1 1 1 编号到 2 n 2n 2n。 编号为 2 i − 1 2i-1 2i1和$ 2i$的代表属于第 i i i个党派。

任务:写一程序读入党派的数量和关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。

输入输出样例

**输入 **

第一行有两个非负整数 n,m。他们各自表示:党派的数量 n和不友好的代表对 m。

接下来 m 行,每行为一对整数 a,b,表示代表 a,b互相厌恶。

4 3
1 4
2 3
7 3

**输出 **

1
4
5

分析

原问题可以描述为有 n n n个组,第 i i i个组里有节点 A i A_i Ai A i ′ A_i' Ai,因为有条件限制使得每个节点不相容。你的任务是每个组里选出一个节点,保证选出的 n n n个节点都相容。

构图:

根据图中给出的关系,构造一个 2 ∗ n 2*n 2n的有向图,边 < x , y > <x,y> <x,y>表示若选了 x x x则必须选择 y y y

对于该题目给出的样例数据进行分析

总共有编号1,2,3,4,5,6,7,8八个人,其中两两一组。

  • 样例数据说1,4不和,又因为每个组里必须要出一个人,所以如果选择了1,就不能选择4,只能选择3,所以 < 1 , 3 > <1,3> <1,3>之间有一条有向边,那么如果选择了4,则一定不能选择1,只有选择2。

每一个限制条件都这样分析,那么会够成下面这个图:

在这里插入图片描述

通过观察所构成的图,会发现5,6这个两个点尚未确定,那该怎么去求得满足限制条件的解呢?

求解强连通分量

通过观察上图可以发现,此图1,3构成一个环,2,4构成一个环,这样1,3要么都被选择,要么都不被选择,2,4同样如此。这时1,3与2,4就构成了一个强连通分量。如果 1 , 2 1,2 1,2在一个强连通分量中,那么就违反了题目的每一个国家只能有一个参会人员的要求。赋值时同样遵循在同一个国家内的两点选择连通分量较小的那一个即可。

总结

处理 2 − S A T 2-SAT 2SAT问题的关键就是建图,而建图的关键是对确定关系的把握,这一点需要大量做题去找感觉。

附上题单:

P4782 【模板】2-SAT 问题

P4171 [JSOI2010]满汉全席

P5782 [POI2001] 和平委员会

P3825 [NOI2017] 游戏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值