2-SAT详解

从例题开始

题目链接【模板】2-SAT 问题

题意

现有n个布尔变量,有m个条件,格式为x1=true/flase∣x2=true/false

求出是否有可行解并输出

算法思想

我们定义u的相对元素为u′,即u!=u′

定义一个集合含有两个元素,即在同一组条件内的两组元素

设该集合A={u,v}

则,如果我们没有选择u,则一定要选择v,如果已经选择了u,则不必选择v

而根据相对元素的原理,如果我们选择了u′,那我们一定要选择v,反之亦然

综上所述,我们可得2-SAT问题的建立规则

选择某一个元素的相对元素,则必须要选择该元素所在集合内的另外一个元素

代码实现

不难发现,元素之间包含着依赖关系

则,我们可以把这些有依赖关系的元素(即2-SAT的建立规则)连一条边,构成一个图

问题1:如何判断当前的图(条件)是否有解

不难发现,如果一个强连通分量内如果有依赖关系,则会导致矛盾,该情况无解

PS:强连通分量内任意两个点都能够互相到达

而强连通分量一词的出现则提醒我们可以使用tarjan算法求强连通分量

而求强连通分量时我们通常会记录每一个点是属于哪一个强连通分量内的

此时,我们设u的对应元素为u+n

那我们则可以判断u和u+n是否在一个强连通分量内,如果在,则无解

否则有解

问题2:怎样求出当前条件下的任意一组解

我们知道,tarjan算法中的dfn序是拓扑序的逆序,而拓扑序越大,则可能性越多,所以我们需要让dfn序尽量小

而在tarjan算法中所求得的强连通的编号越小,则代表该强连通分量内的dfn序越小,则更优

所以我们只需要比较当前点u与其对应元素u+n强连通分量的大小来决定答案

如果u小的话,则用u所表示的答案,反之亦然

贴代码~~~

//这里设x=1时的元素为u,其对应元素(x=0)为u+n
#include <bits/stdc++.h>
using namespace std;
const int N=2000010;//开两倍空间
int n,m;
int h[N],e[N],ne[N],idx;
int dfn[N],low[N],timestamp;
int stk[N],top=0;
bool in_stk[N];
int scc_cnt,id[N];
void add(int a,int b)//加a->b的边
{
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void tarjan(int u)//tarjan求强连通分量
{
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j])low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        int y;
        scc_cnt++;
        do
        {
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
        }while(y!=u);
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int u,x,v,y;
        scanf("%d %d %d %d",&u,&x,&v,&y);
        if(x&&y)//加边的四种情况
        {
            add(u+n,v);
            add(v+n,u);
        }
        if(x&&!y)
        {
            add(u+n,v+n);
            add(v,u);
        }
        if(!x&&y)
        {
            add(u,v);
            add(v+n,u+n);
        }
        if(!x&&!y)
        {
            add(v,u+n);
            add(u,v+n);
        }
    }
    for(int i=1;i<=2*n;i++)
        if(!dfn[i])
            tarjan(i);
    for(int i=1;i<=n;i++)//判断无解情况
        if(id[i]==id[i+n])
        {
            cout<<"IMPOSSIBLE"<<endl;
            return 0;
        }
    cout<<"POSSIBLE"<<endl;
    for(int i=1;i<=n;i++)//寻找一组解
        if(id[i]<id[i+n])cout<<1<<" ";
        else cout<<0<<" ";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值