2-SAT—学习笔记

Q:
2-SAT的问题描述为
有n个变量,每个变量可取值0或1
给定m个限定条件
求是否存在一组合法的赋值方案

A:
为简化描述,记 A i , 0 , A i , 1 分别表示第i个变量取值0和1
在2-SAT问题中
我们可以将所有限制条件转化为统一形式
若有 A i 赋值为 A i , p 则变量 A j 必须赋值为 A j , q

对每个条件进行相应转化后
我们就可以通过图论解决上述问题

对每个变量 A i ,我们把它拆成两个点
结点 i 表示 A i , 0 ,结点 i + n 表示 A i , 1

对每个条件若有 A i 赋值为 A i , p 则变量 A j 必须赋值为 A j , q
我们由 i + p n j + q n 连边

建完图后我们用tarjan求出所有强连通分量
若由 A i , 0 A i , 1 属于同一个强连通分量
表示若有 A i 赋值为0则变量 A i 必须赋值为1
显然产生矛盾
说明在该组限制条件下不存在合法赋值方案
若不存在上述情况则可判定存在合法方案

上述是2-SAT的基本思想
那么对于各种情况的限制条件要如何进行转化呢
我们来看下面这道例题


POJ - 3678 Katu Puzzle
这道题中的限制条件都是形如 x a   o p   x b = c 的算式
c取值0或1,op为OR或AND或XOR

1. a   o r   b = 1
连边a到b+n,表示若a=0,则b必须等于1
连边b到a+n,表示若b=0,则a必须等于1

2. a   o r   b = 0
该条件比较特殊,要求必须a=b=0
我们连边a+n到ab+n到b
(比较玄学)

3. a   a n d   b = 1
这里要求必须a=b=1
连边a到a+nb到b+n

4. a   a n d   b = 0
连边a+n到b,表示若a=1,则b必须等于0
连边b+n到a,表示若b=1,则a必须等于0

5. a   x o r   b = 1
连边a到b+n,表示若a=0,则b必须等于1
连边b到a+n,表示若b=0,则a必须等于1
连边b+n到a,表示若b=1,则a必须等于0
连边a+n到b,表示若a=1,则b必须等于0

6. a   x o r   b = 0
连边a到b,表示若a=0,则b必须等于0
连边b到a,表示若b=0,则a必须等于0
连边b+n到a+n,表示若b=1,则a必须等于1
连边a+n到b+n,表示若a=1,则b必须等于1

这里已经包括了所有限制条件可能的形式了
任何形式的条件都可以通过一定转换得到上述模型

#include<iostream>
#include<stack>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=4000;
int n,m;
struct node{int v,nxt;}E[4000010];
int head[maxn],tot;
int low[maxn],dfn[maxn],cnt;
int col[maxn],ins[maxn],colnum;
stack<int> st;
char ss[10];

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void tarjan(int u)
{
    low[u]=dfn[u]=++cnt;
    st.push(u); ins[u]=1;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]);}
        else if(ins[v])
        low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int v; colnum++;
        do{
            v=st.top();
            st.pop(); ins[v]=0;
            col[v]=colnum;
        }
        while(v!=u);
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int u=read()+1,v=read()+1,c=read();
        scanf("%s",&ss);
        if(ss[0]=='A')
        {
            if(c==0)add(u+n,v),add(v+n,u);
            else if(c==1)add(u,u+n),add(v,v+n);
        }
        else if(ss[0]=='O')
        {
            if(c==1)add(u,v+n),add(v,u+n);
            else if(c==0)add(u+n,u),add(v+n,v); 
        }
        else if(ss[0]=='X')
        {
            if(c==1)add(u,v+n),add(v,u+n),add(v+n,u),add(u+n,v);
            else if(c==0)add(u,v),add(v,u),add(u+n,v+n),add(v+n,u+n);
        }
    }

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

    for(int i=1;i<=n;++i)
    if(col[i]==col[i+n]){printf("NO");return 0;}

    printf("YES");
    return 0;
}

最后一个要讨论的问题是如何解决方案输出
tarjan求出强连通分量后
实际上得到的SCC编号SCC缩点后得到新图的反向拓扑序

对于每个 i [ 1 , n ]
我们只需比较 c o l [ i ] , c o l [ i + n ] 拓扑序大小
给i赋值为拓扑序较大的那个

for(int i=1;i<=n;++i)
printf("%d ",col[i]>col[i+n]);

特别注意tarjan求SCC后得到的是反向拓扑序
要比较的是拓扑序

可以拿这道模板题练练手
洛谷 P4782 【模板】2-SAT 问题

#include<iostream>
#include<stack>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=2000010;
int n,m;
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int low[maxn],dfn[maxn],cnt;
int col[maxn],ins[maxn],colnum;
stack<int> st;

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void tarjan(int u)
{
    low[u]=dfn[u]=++cnt;
    st.push(u); ins[u]=1;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(ins[v])
        low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int v; colnum++;
        do
        {
            v=st.top();
            st.pop(); ins[v]=0;
            col[v]=colnum;
        }
        while(v!=u);
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int x1=read(),v1=read(),x2=read(),v2=read();
        add(x1+(v1^1)*n,x2+v2*n);
        add(x2+(v2^1)*n,x1+v1*n);
    }
    for(int i=1;i<=n<<1;++i)
    if(!dfn[i])tarjan(i);

    for(int i=1;i<=n;++i)
    if(col[i]==col[i+n]){printf("IMPOSSIBLE");return 0;}

    printf("POSSIBLE\n");
    for(int i=1;i<=n;++i)
    printf("%d ",col[i]>col[i+n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/niiick/article/details/81429076