2-SAT问题介绍求解 + 模板题P4782

(点击此处查看原题)

什么是2-SAT问题

sat 即 Satisfiability,意思为可满足,那么2-SAT表示一些布尔变量只能取true或者false,而某两个变量之间的值存在一定的关系(如:只要a为真,b一定为假;如果a为假,b也为假),我们需要在满足所有这样的关系的情况下,求出每个变量的赋值,如果不存在,就是无解。

举个栗子

某一天,PC,YD,HL在讨论两个问题:1)winter的温度是否低于0度,2)bamboo的高度是否大于10m,众所周知,这三位大佬很强大,所以他们对于这两个问题的判断至少有一个是正确的。此时给出三人对这两个问题的判断,求出这两个问题的正确答案;如果不存在正确答案,那么可能是某个人犯糊涂了,因此我们无法得到答案,输出无解。

我们将两个问题符号化为,a:winter的温度低于0度, b :bamboo的高度大于10m,将三人的观点及其符号化表示如下:

1)PC认为:winter的温度低于0度,bamboo的高度大于10m,a∨ b

2)YD认为:wintet的温度不低于0度,bamboo的高度大于10m,¬a∨ b

3)HL认为:winter的温度不低于0度,bamboo的高度不大于10m,¬a∨ ¬b

 那么a,b的取值需要满足:(a∨ b) ∧ ( ¬a∨ b) ∧ (¬a∨ ¬b),我们如何求出满足这个式子的a,b的取值这类问题就是2-SAT问题。

将2-SAT问题转为图论问题求解

我们可以将每个变量x的两个状态分别用两个点表示,记编号为i的点表示这个变量取真,i+n的点表示这个变量取假

将两个变量之间的关系用边表示,如:只要a为真,b一定为假,将此命题符号化得到¬a¬b,这个式子也可以写成:a → ¬b ∧ b → ¬a,表示:a为真,则b必为假 ,和b为真,则a必为假,这样我们就发现 a 和 ¬b 可以由a推出¬b, 可以由b推出¬a,在图中,我们构建这样的边表示他们的关系:由 a 向  ¬b 建一条单向边,再由 b 向 ¬a建一条单向边,总结以下将命题转化为建图的规律:

1)¬a¬b   ----> a → ¬b ∧ b → ¬a 

2)ab   ----> ¬a → b ∧ ¬b → a

3)¬ab   ----> a → b ∧ ¬b → ¬a 

4)a∨¬b   ----> ¬a → ¬b ∧ b → a 

(x→y可以视作由x向y建一条单向边)

然后,我们在这个图中求强连通分量,联想到我们图中边代表的关系:由边的起点可以推出终点,所以同一强连通分量中的点真值一致,所以我们很容易想到如果 a 和 ¬a在同一强连通分量中,这说明a和¬a真值相同,这显然是不正确的,即无解;如果不存在变量x使得x和¬x在同一强连通分量中,就说明有解。

那么有解的情况下,我们如何得到每个变量的值呢?只要x所在强连通分量的拓扑序比¬x所在强连通分量的拓扑序靠后,则x为真,否则为假,而我们用tarjan算法求强连通分量的时候,对于每个强连通分量的标记是逆拓扑序的,所以 node[x] < node[¬x] 时,x取真,否则为假,这样一来,我们对每个变量进行判断并输出对应的值即可

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>

#define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const ll inf = 2e9 + 10;
const ll mod = 1e9 + 7;
const int Max = 2e6 + 10;
const int Max2 = 3e2 + 10;

struct Egde
{
    int to, next;
} edge[Max];

int n, m;
int head[Max], tot;
int dfn[Max], low[Max],time_clock;
int line[Max],now;
int node[Max],sccCnt;

void init()
{
    memset(head, -1, sizeof(head));
    tot = 0;
    memset(dfn,0,sizeof(dfn));
    time_clock = 0;
    now = 0;
    memset(node,0,sizeof(node));
    sccCnt = 0;
}

void add(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++time_clock;
    line[++now] = u;
    for(int i = head[u] ; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }
        else if(!node[v])
        {
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(dfn[u] == low[u])
    {
        sccCnt++;
        while(line[now] != u)
            node[line[now--]] = sccCnt;
        node[line[now--]] = sccCnt;
    }
}

int main()
{
#ifdef LOCAL
//        freopen("input.txt", "r", stdin);
//        freopen("output.txt", "w", stdout);
#endif
    while (scanf("%d%d", &n, &m) != EOF)
    {
        init();
        for (int i = 1, a, va, b, vb; i <= m; i++)
        {
            scanf("%d%d%d%d", &a, &va, &b, &vb);
            add(a + n * (va & 1), b + n * (vb ^ 1));
            add(b + n * (vb & 1), a + n * (va ^ 1));       //1~n表示1,n+1,2*n表示0
        }
        for(int i = 1;i <= (n << 1) ;i ++)
        {
            if(!dfn[i])
                tarjan(i);
        }
        bool ok = true;
        for(int i = 1;i <= n ;i ++)
        {
            if(node[i] == node[i+n])
            {
                ok = false;
                break;
            }
        }
        if(!ok)
        {
            printf("IMPOSSIBLE\n");
        }
        else
        {
            printf("POSSIBLE\n");
            for(int i = 1;i < n ;i ++)
            {
                printf("%d ",node[i] < node[i+n]);
            }
            printf("%d\n",node[n] < node[n<<1]);
        }
    }
    return 0;
}
View Code

转载于:https://www.cnblogs.com/winter-bamboo/p/11402634.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值