2-SAT

· · 算法·理论

2-SAT

(零)引入

2-SAT 算法用于求解逻辑方程组,如:

n 个01变量 x_i,给出 m 组关系:x_i=ax_j=b,其中 a,b\in\{0,1\}

求一组解,使得满足所有条件。

(一)算法简介

将限制转化为图,比如对于:x=1y=1,若 x=0,则一定 y=1;若 y=0,则一定 x=1

其实就是 a\implies b 等价于 \neg a\vee b。即 a\implies b 为假当且仅当 a 为真、b 为假。

a\vee b 可以拆分为 \neg a\implies b,\neg b\implies a

于是对点 x 开两个点 x,x+n 表示取值 1/0,于是可以对于上面描述建边;

观察发现:

使用 tarjan 缩点,染的颜色是倒拓扑序;于是若 color[x]<color[x+n]x=1

时间复杂度为 O(n+m)

#include<bits/stdc++.h>
#define rg register int
#define fo(i,l,r) for(rg i=l;i<=r;i++)
#define dfo(i,r,l) for(rg i=r;i>=l;i--)
#define frein freopen("in.txt","r",stdin);
#define freout freopen("out.txt","w",stdout);
#define fre(p) freopen(#p".in","r",stdin),freopen(#p".out","w",stdout);
#define outa(l,r,a) {fo(i,l,r)cout<<a[i]<<" ";cout<<"\n";}
#define outm(l1,r1,l2,r2,a) {fo(i,l1,r1){fo(j,l2,r2)cout<<a[i][j]<<" ";cout<<"\n";}cout<<"\n";}
#define BZ cout<<"----------------\n";
#define ll long long
using namespace std;
void rd(int &x){
    int v(0),op(1);
    register char c=getchar();
    while(c<'0'||'9'<c)op=c=='-'?-1:1,c=getchar();
    while('0'<=c&&c<='9')v=(v<<1)+(v<<3)+(c^48),c=getchar();
    x=v*op;
}
void no(){printf("IMPOSSIBLE");exit(0);}
const int N=2e6+5;
int n,m,x,px,y,py;
int sum,to[N<<1],nex[N<<1],hd[N];
void add(int x,int y){to[++sum]=y,nex[sum]=hd[x],hd[x]=sum;}
int cnt,dfn[N],low[N],st[N],l,col[N],cl;bool bz[N];
void tarjan(int x){
    dfn[x]=low[x]=++cnt,bz[x]=1,st[++l]=x;
    for(rg i=hd[x];i;i=nex[i]){
        if(dfn[to[i]]==0)tarjan(to[i]),low[x]=min(low[x],low[to[i]]);
        if(bz[to[i]]==1)low[x]=min(low[x],dfn[to[i]]);
    }
    if(dfn[x]==low[x]){
        ++cl;
        do{col[st[l]]=cl,bz[st[l--]]=0;}while(st[l+1]!=x);
    }
}
int main(){
    rd(n),rd(m);
    fo(i,1,m){
        rd(x),rd(px),rd(y),rd(py);
        if(px==1&&py==1)add(x+n,y),add(y+n,x);
        if(px==1&&py==0)add(x+n,y+n),add(y,x);
        if(px==0&&py==1)add(x,y),add(y+n,x+n);
        if(px==0&&py==0)add(x,y+n),add(y,x+n);
    }
    fo(i,1,2*n)if(dfn[i]==0)tarjan(i);
    fo(i,1,n)if(col[i]==col[i+n])no();
    printf("POSSIBLE\n");
    fo(i,1,n)printf("%d ",(col[i]<col[i+n]));
    return 0;
}

(二)应用与例题

Gmoj.8097. 不平衡的对战

题面

"水”和“火”互相克制 “佬”分别克制“水”“火”“菜”三种属性 “佬”“水”“火”三种属性都克制“菜” 其它上述没有提到的都不克制 已知 $m$ 对是否克制的关系,构造每个点的属性; $n,m\le 5\times 10^5$; #### 题解 观察题面,发现是若干组限制关系,且为构造,想到 2-SAT; 但是共有 $4$ 种属性,考虑拆成 $2$ 个 $0/1$ 的数来表示; 比如: 佬 $=\{1,1\}$; 菜 $=\{0,0\}$; 水 $=\{0,1\}$; 火 $=\{1,0\}$; 看看怎么表达“克制”关系,设:第 $x$ 个点的第 $1/2$ 个数为 $a[x][0/1]$; 那么 $x$ 克制 $y$ 当且仅当 $a[x][0]>a[y][0]$ 或 $a[x][1]>a[y][1]$; 而 $x$ 不克制 $y$ 当且仅当 $a[x][0]\le a[y][0]$ 且 $a[x][1]\le a[y][1]$; 对于 $x$ 不克制 $y$:其实是两条独立的限制, 可以发现:若 $a[x][0]=1$,则 $a[y][0]=1$ 等关系; 对于 $x$ 克制 $y$:若 $a[x][0]=0$,则 $a[x][1]=1$ 且 $a[y][1]=0$…,这个其实也可以拆成两条独立的限制; 于是把 $x$ 拆成 $4$ 个点,按上述建边,跑 2-SAT 即可。 ### P3825 [NOI2017] 游戏 细节比较多,多调了 1.5h,思路很好想,就 $2^d$ 枚举是 $A/B$ 还是 $B/C$,剩下了一些限制。 自己做的时候的思路是比较复杂的,因为我将点分为了两种: 1.可以通过当前关系直接确定的,2.不能直接确定,需要跑 2-SAT 的点。 然后我就设计了一个很复杂的拓扑来去除 1 类点,这带来了很多细节。 但是完全不需要这样复杂,**我们可以仅通过 2-SAT 的建图就能处理。**而在外面强加一个拓扑就是画蛇添足了。 关于 2-SAT 的建图有一点需要注意的:题目限制形如:**若 $a_x=u$ 则 $a_y=v$,其实隐含着若 $a_y\not=v$ 则 $a_x\not= u$。** 这点需要注意。