2-SAT 总结

2-SAT 问题详解

2-SAT 的典型形式

每个约束条件长这样:

(x₁ 为真) 或 (x₂ 为假)
(x₃ 为真) 或 (x₄ 为真)
(变量a = 某值) 或 (变量b = 某值)

用逻辑符号表示就是:(a ∨ b) 或者 (a ∨ ¬b) 等等。

核心思想:转化为图论问题

第一步:建立蕴含图

关键公式:(a ∨ b) 等价于 (¬a → b) ∧ (¬b → a)

意思是:如果 a 为假,那么 b 必须为真;如果 b 为假,那么 a 必须为真。

直觉理解:约束 (a ∨ b) 说“a 和 b 至少一个为真”。那如果我发现 a 是假的,为了满足这个约束,b 就必须是真的。反过来也一样。

第二步:图的构建

  • 每个变量 x 拆成两个点:x 表示 x 为真,¬x 表示 x 为假
  • 对于约束 (a ∨ b),添加两条有向边:
    • ¬a → b(a 假则 b 真)
    • ¬b → a(b 假则 a 真)

为什么不是

  • ¬a → b(a 假则 b 真)
  • a → ¬b(a 真则 b 假)

呢?因为其实可以 a 和 b 都是真,这样建图会导致 a 和 b 必然一正一负,显然不符。

当然,我们也可以简单建图,背下来即可:

adde(x+n*(vx&1),y+n*(vy^1));
adde(y+n*(vy&1),x+n*(vx^1));

第三步:判断可满足性

如果存在变量 x,使得 x¬x 在同一个强连通分量(SCC)中,那么问题无解。

为什么? 因为在 SCC 中,所有点可以互相到达。x 能到达 ¬x 意味着“x 为真能推出 x 为假”,反之亦然。这产生了逻辑矛盾:x 真 ⇒ x 假,同时 x 假 ⇒ x 真。无论 x 取什么值都会导出相反的要求,所以不可能满足。

第四步:构造解

如果可满足,按照 SCC 的拓扑序(从后往前)赋值:如果 x 所在 SCC 的拓扑序在 ¬x 之后,则 x 为真,否则为假。

为什么这样赋值正确? 一条边 u → v 的逻辑含义是“u 真则 v 真”。在拓扑序中,边总是从序号小的 SCC 指向序号大的 SCC。如果我们按拓扑序从大到小(后往前)处理,先决定的点不会依赖后决定的点。当我们处理 x 时,如果 x 的拓扑序比 ¬x 大,说明 ¬x → x 的路径不存在(否则它们会在同一 SCC,或者 ¬x 的拓扑序更大),那我们把 x 设为真就不会被迫把 ¬x 也设为真,从而避免矛盾。

不会证明也没有关系,试一试就行。

一个简单的验证:

3 2
1 1 3 0
1 1 3 1

第一位应该是 1。


#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,tot;
ll dfn[2000005],low[2000005],st[2000005],tail,in[2000005],scccnt,id[2000005];
vector<ll>ve[2000005];
void tarjan(ll x){
    dfn[x]=low[x]=++tot;
    st[++tail]=x;
    in[x]=1;
    for(int v:ve[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(in[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        scccnt++;
        ll y;
        do{
            y=st[tail];
            tail--;
            in[y]=0;
            id[y]=scccnt;
        }while(y!=x);
    }
}
void adde(ll x,ll y){
    ve[x].push_back(y);
}
int main(){
    cin>>n>>m;
    ll x,vx,y,vy;
    for(int i=1;i<=m;i++){
        cin>>x>>vx>>y>>vy;
        adde(x+n*(vx&1),y+n*(vy^1));
        adde(y+n*(vy&1),x+n*(vx^1));
    }
    for(int i=1;i<=n*2;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++){
        if(id[i]==id[i+n]){
            cout<<"IMPOSSIBLE";
            return 0;
        }
    }
    cout<<"POSSIBLE\n";
    for(int i=1;i<=n;i++){
        if(id[i]<id[i+n]){
            cout<<1<<" ";
        }
        else{
            cout<<0<<" ";
        }
    }
}
posted @ 2026-08-14 21:09  MistyPost  阅读(4)  评论(0)    收藏  举报