只有2-SAT不是NPC呢——2-SAT学习笔记

\[{\Huge\text{2-SAT学习笔记}}\]


什么是2-SAT?

我们举一个简单的例子:

机房里有三位大佬小s,小l,小m和一个蒟蒻tqr,他们刷题时有不同的要求(因为蒟蒻什么题都不会做,故不列举):

大佬/要求小s小l小m
要求1不包含数论知识(¬a)包含数论知识(a)不包含数论知识(¬a)
要求2包含几何知识(b)包含几何知识(b)不包含几何知识(¬b)
要求3不包含图论知识(¬c)不包含图论知识(¬c)包含图论知识(c)

在此之前,复习一下前置数学知识:

对于一个命题:如果a,那么b

其逆命题为:如果b,那么a

其否命题为:如果¬a,那么¬b

其逆否命题为:如果¬b,那么¬a

∧表示与,∨表示或,¬表示非

原命题与逆否命题等值。逆命题与否命题等值

原命题与逆命题以及逆否命题与否命题之间没有关系

我们继续上面的情景,如果我们要出一道题,使这道题能同时满足上面三位dalao的喜好,该如何做?

经过数学分析,满足的题目一定符合以下条件(¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c)

因此我们要做的就是给每个变量赋值,使上式值为true

是的,这就是SAT问题,但需要注意的是,上面的情况中,每个同学对题目都有三个限制,因此是3-SAT问题

可证明的,3(及以上)-SAT问题都是NPC问题(即不可使用算法解决,唯一的方法是暴力枚举)

因此,2-SAT问题是算法能解决的极限,那么我们将限制改一改:

经过一段时间的学习后,除了tqr,其他人都完全掌握了图论知识……

一波操作过后,限制条件变成了(¬a∨b)∧(a∨b)∧(¬a∨¬b),这就是我们要学习的2-SAT了

那我们就开始吧……

如何解决2-SAT?

首先,我们需要将几个互相有限制的点赋值,那就把它们丢到图里面!

建立两个点,分别是a和¬a(储存时,可以存到编号分别为 \(i\)\(i+maxn\) 的节点上,就像并查集对称点一样)

那么每个点之间的关系是什么?

(a∨b)可以理解为若a为真,则b为假,反之b为真

于是我们可以建出图形:

¬a→ b∧¬b →a

o_2sat1.png

由图形可以发现,a与¬b,以及¬a与b都在同一个强连通分量里,于是我们可以得出结论:

2-SAT问题同一个强连通分量中的所有元素值相同

很显然的,如果a与¬a(或者x与¬x,x代表任意条件)在同一个强连通分量里,即他们的值相等,那么出现了矛盾,可判断问题无解(就像1=-1一样,矛盾的等式)

解决一个实际问题

题目描述

有n个布尔变量 \(x_1\)~\(x_n\),另有 \(m\) 个需要满足的条件,每个条件的形式都是 \(x_i\)true/false\(x_j\)true/false。你的mubiao给每个变量赋值使得所有条件得到满足。

输入格式

第一行两个整数\(n\)\(m\),意义如题面所述

接下来\(m\)行每行\(4\)个整数\(i,a,j,b\),表示\(x_i\)\(a\)\(x_j\)\(b\)\((a,b∈\{0,1\})\)

输出格式

如无解,输出\(IMPOSSIBLE\);否则输出\(POSSIBLE\),

下一行\(n\)个整数\(x_1\)~\(x_n\)\((x_i∈\{0,1\})\),表示构造出的解。

很显然地,我们需要按照上述所说的来建边

read(n);read(m);
for(register int i=1;i<=m;++i)
{
    read(a),read(va),read(b),read(vb);
    add_edge(a+!va*n,b+vb*n);
    add_edge(b+!vb*n,a+va*n);
}

然后tarjan找强连通分量(color数组是拓扑序)

void tarjan(int u)
{
    low[u]=dfn[u]=++idx;
    sta.push(u);
    vis[u]=1;
    for(register int i=head[u];i;i=edge[i].next)
    {
        int v=edge[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])
    {
        for(tms++;!sta.empty();)
        {
            int x=sta.top();
            sta.pop();
            vis[x]=0;
            color[x]=tms;
            if(x==u) break;
        }
    }
}

for(register int i=1;i<=2*n;++i) if(!dfn[i]) tarjan(i);

如果强连通分量 \(x_0\), \(x_1\) 在缩点后 \(DAG\) 的底图中连通,那么我们选择的一定是拓扑序较大的

\(Tarjan\) 求强连通分量得到的强连通分量编号是遵循拓扑序逆序的,所以如果 \(x_0\) 的编号更小,输出 \(0\),否则输出 \(1\)

for(register int i=1;i<=n;++i)
    if(color[i]==color[i+n])
        {puts("IMPOSSIBLE");return 0;}  puts("POSSIBLE");
for(register int i=1;i<=n;++i)
    printf(color[i]>color[i+n]?"1 ":"0 ");

这道题就很轻松地解决了,下面是完整代码

#include<bits/stdc++.h>
using namespace std;

int n,m,a,va,b,vb;

struct Edge
{
    int next,to;
}edge[2000005];
int cnt=0,head[2000005];

inline void add_edge(int from,int to)
{
    edge[++cnt].next=head[from];
    edge[cnt].to=to;
    head[from]=cnt;
}

int low[2000005],dfn[2000005],color[2000005],tms,idx;
bool vis[2000005];
stack<int> sta;
void tarjan(int u)
{
    low[u]=dfn[u]=++idx;
    sta.push(u);
    vis[u]=1;
    for(register int i=head[u];i;i=edge[i].next)
    {
        int v=edge[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])
    {
        for(tms++;!sta.empty();)
        {
            int x=sta.top();
            sta.pop();
            vis[x]=0;
            color[x]=tms;
            if(x==u) break;
        }
    }
}

template<class T>inline void read(T &res)
{
    T flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}

int main()
{
    read(n);read(m);
    for(register int i=1;i<=m;++i)
    {
        read(a),read(va),read(b),read(vb);
        add_edge(a+!va*n,b+vb*n);
        add_edge(b+!vb*n,a+va*n);
    }
    for(register int i=1;i<=2*n;++i) if(!dfn[i]) tarjan(i);
    for(register int i=1;i<=n;++i)
        if(color[i]==color[i+n]) {puts("IMPOSSIBLE");return 0;}
    puts("POSSIBLE");
    for(register int i=1;i<=n;++i)
        printf(color[i]>color[i+n]?"1 ":"0 ");
    return 0;
}

有什么不懂的可以发qq问我,没了

o_%E9%AA%97%E5%85%B3%E6%B3%A8.png

o_%E9%AA%97%E8%B5%8F%E9%93%B62.png

转载于:https://www.cnblogs.com/tqr06/p/11191174.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值