2-SET详解

前置知识

SET问题的标准定义:在计算机科学中,布尔可满足性问题(有时称为命题可满足性问题,缩写为SATISFIABILITY或SAT)是确定是否存在满足给定布尔公式的解释的问题。(全是废话)

说人话就是,你要给n个变量,n需要给他赋值使它满足给你一些形如(x1为1或x3为0或x4为3)的条件,你必须满足所有条件(满足例子的条件需要x1为1,x3为0,x4为3中至少有一个成立)。

但这个问题并不好解决,一旦变量的取值范围变大时间复杂度将不可估量。所以我们考虑最简单的2-sat问题(1-sat大家都会)

2-SAT思路

1.因为2-sat只有两种取值所以一个变量分为x1和x2两个代表取第一种值和第二种值(以下用0,1代表)

 

 2.对于每一个条件如(x1为1或x2为0)可以引申出两个条件若x1为0则x2一定为0,若x2为1则x1为1,我们将x1为0向x2为0以及x2为1向x1为1连边。

 3.将所有条件按上面的方法处理后,做强联通分量,如果同一个变量的两个取值在一个强联通分量则意味从变量的0可以推到1,从1可以推到0,则取任何值都不满足条件。如果不在则可以确定变量取值为拓扑序大的数,因为从取值1推到取值2,取值2推不倒取值1,则选择取值2满足条件。

 

 

 上图条件(x1为1或x2为0)(x1为0或x3为0)(x1为0或x3为0)

答案为x1为0,x2为0,x3为0(答案不唯一),绿色为一种可能的拓扑序,由绿色的拓扑序可推出上面的答案

 例题

P4782 【模板】2-SAT - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int n,m,cnt,ys,nxt[2*N],hd[2*N],go[2*N],tot,dfn[2*N],low[2*N],col[2*N],ans[N],top,sta[2*N],vis[2*N];
void add(int x,int y)
{
    nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
    return ;    
}
void tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    sta[++top]=u;
    vis[u]=1;
    for(int i=hd[u];i;i=nxt[i])
    {
        int v=go[i];
        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])
    {
        ys++;
        while(sta[top]!=u)
        {
            col[sta[top]]=ys;
            vis[sta[top]]=0;
            top--;
        }
        col[sta[top]]=ys;
        vis[sta[top]]=0;
        top--;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,az,b,bz;scanf("%d%d%d%d",&a,&az,&b,&bz);
        add(a+(az&1)*n,b+(bz^1)*n);//1到n为第i个变量选1,n+1到n+n为第i-n个变量选0
        add(b+(bz&1)*n,a+(az^1)*n);//简单的建图法,不懂的自己分情况验证
    }
    for(int i=1;i<=2*n;i++)
        if(!dfn[i])tarjan(i);
    for(int i=1;i<=n;i++)
    {
        if(col[i]==col[i+n])//在同一强联通分量中
        {
            printf("IMPOSSIBLE\n");
            return 0;
        }
    }
    printf("POSSIBLE\n"); 
    for(int i=1;i<=n;i++)
    {
        if(col[i]>col[i+n])printf("0 ");//tarjan的是倒序,所以选小的
        else printf("1 ");
    }
    return 0;
}

 P4171 [JSOI2010] 满汉全席 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

几乎是模板题,改输入就行了。

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int k,n,m,cnt,ys,nxt[2*N],hd[2*N],go[2*N],tot,dfn[2*N],low[2*N],col[2*N],top,sta[2*N],vis[2*N];
void add(int x,int y)
{
    nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
    return ;    
}
void tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    sta[++top]=u;
    vis[u]=1;
    for(int i=hd[u];i;i=nxt[i])
    {
        int v=go[i];
        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])
    {
        ys++;
        while(sta[top]!=u)
        {
            col[sta[top]]=ys;
            vis[sta[top]]=0;
            top--;
        }
        col[sta[top]]=ys;
        vis[sta[top]]=0;
        top--;
    }
}
int main()
{
    scanf("%d",&k);
    while(k--)
    {
    memset(hd,0,sizeof(hd));
    memset(dfn,0,sizeof(dfn));
    ys=tot=cnt=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        char aa[5],bb[5];
        int a=0,b=0,az,bz;
        scanf("%s%s",aa,bb);
        int c=1;
        while(aa[c]>='0'&&aa[c]<='9')
            a=a*10+aa[c]-'0',c++;
        c=1;
        while(bb[c]>='0'&&bb[c]<='9')
            b=b*10+bb[c]-'0',c++;
        if(aa[0]=='m')az=0;
        else az=1;
        if(bb[0]=='m')bz=0;
        else bz=1;
        add(a+(az&1)*n,b+(bz^1)*n);
        add(b+(bz&1)*n,a+(az^1)*n);
    }
    for(int i=1;i<=2*n;i++)
        if(!dfn[i])tarjan(i);
    int flag=1;
    for(int i=1;i<=n;i++)
    {
        if(col[i]==col[i+n])
        {
            flag=0;
            printf("BAD\n");
            break;
        }
    }
    if(flag)printf("GOOD\n"); 
    }
    return 0;
}

 

posted @ 2023-11-25 14:16  storms11  阅读(46)  评论(0编辑  收藏  举报