知识点 - 2-SAT

本文深入探讨了2-SAT问题,介绍了如何通过图论方法将其转化为有向图,并利用深度优先搜索(DFS)和强连通分量(SCC)算法求解。详细解释了建边规则,展示了如何通过判断变量及其反变量是否在同一SCC来确定问题是否有解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识点 - 2-SAT

概念

SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。

可以证明,当 k>2 时,k-SAT是NP完全的。因此一般讨论的是 k=2 的情况,即2-SAT问题。

我们通俗的说,就是给你n个变量ai,每个变量能且只能取0/1的值。同时给出若干条件,形式诸如(not)aiopt(not)aj=0/1(not)a_iopt(not) a_j=0/1(not)aiopt(not)aj=0/1,其中optoptopt表示and,or,xorand,or,xorand,or,xor中的一种

而求解2-SAT的解就是求出满足所有限制的一组a

首先我们考虑将2-SAT问题往图论的方向靠,我们发现每个点要么取0,要么取1。因此对于aia_iai,我们建两个点2i−12i−12i12i2i2i分别表示aiaiai000111

建边

然后我们考虑建边来表示这些关系,我们令一条有向边的意义:x→yx→yxy表示如果选择了xxx就必须选yyy

那么我们可以举一些简单的例子来总结下连边的规律(用i′i′i表示iii的反面):

  • i,ji,ji,j必须同时选:选了iii就要选jjj,选jjj就要选iii。故i→j,j→ii→j,j→iij,ji。一般操作即为aia_iai xorxorxor aj=0a_j=0aj=0
  • i,ji,ji,j必须且只能 选一个:选了iii就要选j′j'j,选jjj就要选i′i′i,选i′i′i就要选jjj,选j′j′j就要选iii。故i→j′,j→i′,i′→j,j′→ii→j′,j→i′,i′→j,j′→iij,ji,ij,ji。一般操作即为aia_iai xorxorxor aj=1a_j=1aj=1
  • i,ji,ji,j至少 选一个:选个i′i'i就要选jjj,选j′j'j就要选iiiaia_iai ororor aj=1a_j = 1aj=1
  • i,ji,ji,j至多 选一个:选个iii就要选j′j'j,选jjj就要选i’i’iaia_iai andandand aj=0a_j = 0aj=0
  • iii必须选:直接i′→ii′→iii,可以保证无论怎样都选iii。一般操作为给出的ai=1a_i=1ai=1aia_iai andandand aj=1a_j = 1aj=1

建好图然后就是考虑怎么用图论的方式解决2-SAT了。
建好图然后就是考虑怎么用图论的方式解决2-SAT了。

实现

  1. 对于每个当前不确定的变量aia_iai,令ai=0a_i=0ai=0然后沿着边DFS访问相连的点。
  2. 检查如果会导致任意一个jjjj′j′j都被选,那么撤销。否则令ai=0a_i=0ai=0
  3. 否则令ai=1ai=1ai=1,重复2。如果还不行就无解。
  4. 继续考虑下一个不确定的变量

这样的话正确性显然,由于这里的DFS涉及到全局,因此复杂度是O(n(n+m))O(n(n+m))O(n(n+m))的。

一般情况下已经很优秀了,而且还可以改进:

只需要在DFS之前判断i′i′i能否走到iii就可以省略撤销标记的过程,所以我们可以bitset优化传递闭包做到O(n3w)OO(n3w)OO(n3w)O预处理,然后就可以O(n+m)O(n+m)O(n+m)的DFS了。

这种做法还可以保证解的字典序,有时不失为一种不错的方法

考虑我们上面的判断有无解的情况,我们想到完全可以借助SCC来判断两个点是否互相到达。

那么我们先缩点,如果iiii′i′i在同一SCC里那么显然无解。

否则选择iiii′​i′​i拓扑序较大的一个就可以得到一组可行解。

不是很常用(主要是一般题目的数据范围都不需要),用来判可行性比较好。

代码

/*
将每个变量拆成两个点2i和2i+1,分别表示xi为假和xi和真
对于每个条件,连两条对称的有向边。
逐一考虑没有赋值的变量,先假定它为假,然后标记结点2i,然后沿着有向边标记所有能标记的结点
如果标记的过程中 发现某个变量对应的两个结点都被标记,则xi为假这个假定不成立,需要改xi为真
然后重新标记
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;
const int maxn=100+10;
struct TwoSAT{
    int n;
    vector<int>G[2*maxn];
    bool mark[maxn*2];
    int S[maxn*2],c;
    bool dfs(int x){
        if(mark[x^1])return false;
        if(mark[x])return true;
        mark[x]=true;
        S[c++]=x;
        for(int i=0;i<G[x].size();i++){
            if(!dfs(G[x][i]))return false;
        }
        return true;
    }
    void init(int n){
        this->n=n;
        for(int i=0;i<n*2;i++)G[i].clear();
        memset(mark,0,sizeof(mark));
    }
    void add_clause(int x,int xval,int y,int yval){
        x=x*2+xval;
        y=y*2+yval;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }

    bool solve(){
        for(int i=0;i<n*2;i+=2){
            if(!mark[i]&&!mark[i+1]){
                c=0;
                if(!dfs(i)){
                    while(c>0)mark[S[--c]]=false;
                    if(!dfs(i+1))return false;
                }
            }
        }
        return true;
    }
};
int main(){
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值