2-sat 小结

86 篇文章 0 订阅

定义

简略的说, n n n 个变量 a i a_i ai,且 a i ∈ [ 0 , 1 ] a_i\in[0,1] ai[0,1]

给出一些条件,形式如下:

a i a_i ai o p op op a j = 0 / 1 ( o p ∈ [ a n d , o r , x o r ] ) a_j =0/1(op\in[and,or,xor]) aj=0/1(op[and,or,xor])

求解 2 − S A T 2-SAT 2SAT 即找到一组合法的 a a a 满足所有限制。

图论建图

明确问题后,尝试用图论方法去解决。

考虑到一个变量 a i a_i ai 只有 0 / 1 0/1 0/1 两种取值,尝试将一个点 a i a_i ai 进行拆点 P [ 2 ∗ i ] , P [ 2 ∗ i + 1 ] P[2*i],P[2*i+1] P[2i],P[2i+1],分别表示为将 a i a_i ai 取为 0 0 0 1 1 1 两种情况。

定义有向边 x → y x\rightarrow y xy 为选择了 x x x 就必须要选择 y y y ,举个栗子表示一下。

  • a i a_{i} ai x o r xor xor a j = 1 a_j=1 aj=1 ,即 i → j ‾ i\rightarrow \overline{j} ij j → i ‾ j\rightarrow \overline{i} ji
  • a i a_{i} ai x o r xor xor a j = 0 a_j=0 aj=0 ,即 i → j i\rightarrow j ij j → i j\rightarrow i ji
  • a i a_{i} ai o r or or a j = 1 a_j=1 aj=1 ,即 i → j ‾ i\rightarrow \overline{j} ij j → i ‾ j\rightarrow \overline{i} ji , i ‾ → j \overline{i} \rightarrow j ij j ‾ → i \overline{j} \rightarrow i ji
  • a i = 1 a_i=1 ai=1 ,即 i ‾ → i \overline{i} \rightarrow i ii

求解

一、暴力DFS(常用于求最小字典序) ( O ( n ( m + n ) ) ) (O(n(m+n))) O(n(m+n))

遍历每个点,如果当前点 a i a_i ai 未确定,则:

  • a i = 0 a_i=0 ai=0 ,由该点出发遍历其相关的点。
  • 若出现任一 j j j 使得 a j = 0 a_j=0 aj=0 a j = 1 a_j=1 aj=1 同时取到的情况,则返回 f a l s e false false
  • 若出现第 2 2 2 步中的情况,则从 a i = 1 a_i=1 ai=1 出发进行遍历
  • 若第 3 3 3 步也无法完成,则返回无解

若对于所有的 a i a_i ai 均没有出现无解的情况,则可找到答案。

二、SCC缩点

不了解SCC是什么东西的我就emmm。

将所有的点进行缩点,在一个强连通分量内的点是要同时被选中的,如果 a i = 0 a_i=0 ai=0 a i = 1 a_i=1 ai=1 的点在一个SCC中,那么明显无解,这个比暴搜快的多。

在缩点完成后,我们只要选择 a i = 0 a_i=0 ai=0 a i = 1 a_i=1 ai=1拓扑序较大的点即可,至于为什么就不解释了吧。。

例题

缩点模板

题目链接: https://www.luogu.org/problem/P4782

题意:
有n个布尔变量 x 1 x_{1} x1 x n x_{n} xn 另有 m m m 个需要满足的条件,每个条件的形式都是" x i = t r u e / f a l s e x_{i}=true/false xi=true/false " 或者 " x j = t r u e / f a l s e x_{j}=true/false xj=true/false " 。 比如“ x 1 x_{1} x1 为真或 x 3 x_{3} x3 为假 ”。2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
const int maxn=2000005;
const int maxm=2000005;
const int inf=(int) 2e9+7;
int fr[maxm],to[maxm];
int head[maxn],sta[maxn],top,now;
int belong[maxn],qiang,dfn[maxn],cnt;
int n,m,ans[maxn],nex[maxm],low[maxn];
void add(int u,int v){
    to[now]=v;
    nex[now]=head[u]; head[u]=now++;
}
void tarjan(int x){
    dfn[x]=low[x]=++cnt;
    sta[++top]=x;
    for(int i=head[x];~i;i=nex[i]){
        int v=to[i];
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(!belong[v]) low[x]=min(low[x],low[v]);
    }
    if(dfn[x]==low[x]){
        qiang++;
        while(1){
            int u=sta[top--];
            belong[u]=qiang;
            if(u==x) break;
        }
    }
}
int id(int x,int f){
    if(f) return x+n;
    return x;
}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    rep(i,1,m){
        int a,b,f1,f2;
        scanf("%d%d%d%d",&a,&f1,&b,&f2);
        add(id(a,f1^1),id(b,f2));
        add(id(b,f2^1),id(a,f1));
    }
    rep(i,1,2*n) if(!dfn[i]) tarjan(i);
    rep(i,1,n){
        if(belong[i]==belong[i+n]) return 0*printf("IMPOSSIBLE\n");
        if(belong[i]>belong[i+n]) ans[i]=1;
    }
    printf("POSSIBLE\n");
    rep(i,1,n) printf("%d%c",ans[i],i==n?'\n':' ');
    return 0;
}


2018-2019 ACM-ICPC, Asia Seoul Regional Contest K.TV Show Game

题目链接: https://codeforces.com/gym/101987

题意:

数组 a a a n n n 个值 a [ i ] ∈ [ 0 , 1 ] a[i]\in[0,1] a[i][0,1],现在有 m m m 个条件,为 ( ¬ ) a x + ( ¬ ) a y + ( ¬ ) a x > = 2 (\neg)a_x+(\neg) a_y+(\neg)a_x>=2 (¬)ax+(¬)ay+(¬)ax>=2 ,现在问你是否存在一个合法的数组 a a a 满足所有条件。

做法:

因为三个条件里面至少有两个条件要求满足,所以如果某一条件中为 a x a_x ax,那么如果满足 ¬ a x \neg a_x ¬ax ,那么其他两个都要满足要求的值,按照这个条件建边直接跑2-SAT 板子就好了。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rep_e(i,u,v) for(int i=head[u],v=to[i];~i;i=nex[i],v=to[i])
using namespace std;
typedef long long ll;
const int maxn=20005;
const int maxm=10005*8;
int belong[maxn],qiang,dfn[maxn],now;
int n,m,ans[maxn],low[maxn],sta[maxn],top;
int head[maxn],to[maxm],nex[maxm],cnt;
void add(int u,int v){
    to[cnt]=v;nex[cnt]=head[u];
    head[u]=cnt++;
}
void tarjan(int x){
    dfn[x]=low[x]=++now;
    sta[++top]=x;
    for(int i=head[x];~i;i=nex[i]){
        int v=to[i];
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(!belong[v]) low[x]=min(low[x],low[v]);
    }
    if(dfn[x]==low[x]){
        qiang++;
        while(1){
            int u=sta[top--];
            belong[u]=qiang;
            if(u==x) break;
        }
    }
}
int id(int x,int f){
    if(f) return x+n;
    return x;
}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    rep(i,1,m){
        int a,b,c,d,e,f; char x[5],y[5],z[5];
        scanf("%d%s%d%s%d%s",&a,x,&b,y,&c,z);
        d=(x[0]=='R'?1:0);
        e=(y[0]=='R'?1:0);
        f=(z[0]=='R'?1:0);
        add(id(a,d^1),id(b,e));
        add(id(a,d^1),id(c,f));
        add(id(b,e^1),id(a,d));
        add(id(b,e^1),id(c,f));
        add(id(c,f^1),id(a,d));
        add(id(c,f^1),id(b,e));
    }
    rep(i,1,2*n){
        if(!dfn[i]) tarjan(i);
    }
    int flag=0;
    rep(i,1,n){
        if(belong[i]==belong[i+n]) return 0*printf("-1\n");
        if(belong[i]<belong[i+n]) ans[i]=1;
    }
    rep(i,1,n){
        printf("%c",(ans[i]==1?'B':'R'));
    }
    printf("\n");
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值