学习笔记:2-SAT

2-SAT

什么是 2-SAT?

首先,把「2」和「SAT」拆开。SAT 是 Satisfiability 的缩写,意为可满足性。即一串布尔变量,每个变量只能为真或假。要求对这些变量进行赋值,满足布尔方程。

举个例子:假设一群 OIer 在同一个机房里(其实根本不用假设),其中 tsq 为了能够花更多时间在搞颓和玩手机上(是这样的),希望 yb 能满足他的一些要求(例如不能在 tsq 搞颓时打扰他),假设现在有如下要求:

  • tsq:我希望 yb 满足下列条件之一:
    1. 不要带 tsq 玩原神。(¬a
    2. 要带 tsq 搞卷。(b
    3. 不要带 tsq 打 slay。(¬c
  • tsqtsq:我希望 yb 满足下列条件之一:
    1. 要带 tsqtsq 玩原神。(a
    2. 要带 tsqtsq 搞卷。(b
    3. 不要带 tsqtsq 打 slay。(¬c
  • tsqtsqtsq:我希望 yb 满足下列条件之一:
    1. 要带 tsqtsqtsq 玩原神。(a
    2. 不要带 tsqtsqtsq 搞卷。(¬b
    3. 要带 tsqtsqtsq 打 slay。(c

显然真实的 tsq 是第三种。

我们不妨把三种要求设为 abc,变量前加 ¬ 表示「不」,即「假」。上述条件翻译成布尔方程即:(¬ab¬c)(ab¬c)(a¬bc)。其中, 表示或, 表示与。(就像集合中并集交集一样)

现在要做的是,为 abc 三个变量赋值,满足三位 OIer 的要求。

Q: 这可怎么赋值啊?暴力?

A: 对,这是 SAT 问题,已被证明为 NP 完全 的,只能暴力。

Q: 那么 2-SAT 是什么呢?

A: 2-SAT,即每位同学只有两个条件(比如三位 OIer 都对要不要 YB 带打 slay 没有要求,这就少了一个条件)不过,仍要使所有同学得到满足。于是,以上布尔方程当中的 c¬c 没了,变成了这个样子:(¬ab)(ab)(a¬b)

怎么求解 2-SAT 问题?

使用强连通分量。 对于每一个变量 x,我们考虑建立两个点 x¬x 分别表示变量 xtrue 或者 false。所以,图的节点的个数是变量个数的两倍。

在存储方式上,我们将第 i 个变量记为 i,其对应的否定 ¬i 则为 i+n。对于每一个 OIer 的要求 (ab),我们转换为 ¬ab¬ba

对于这个式子,可以感性理解为:a 假则 b 必真,若 b 假则 a 必真

我们考虑这样建图:

原式 建图
¬ab ab¬b¬a
ab ¬ab¬ba
¬a¬b a¬bb¬a

建图之后,容易发现同一强连通分量内的变量值一定是相等的

也就是说,如果 x¬x 在同一强连通分量内部,一定无解。反之,就一定有解了。

但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?

这个很简单,只需要 x 所在的强连通分量的拓扑序在 ¬x 所在的强连通分量的拓扑序之后取 x 为真就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 scc[x] < scc[-x]

时间复杂度:O(N+M)

#include <iostream>
using namespace std;
const int N = 4e6 + 5;
int n, m, a, b, x, y;
struct edge{int to, nxt;}e[N];
int head[N], cnt = 1;
int dfn[N], low[N], scc[N], tot, sum;
int stk[N], top;
bool vis[N];
int read(){
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
void add(int u, int v){
    cnt++;e[cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
void tarjan(int u){
    tot++;top++;
    dfn[u] = tot;low[u] = tot;
    stk[top] = u;
    vis[u] = true;
    for(int i = head[u] ; i != 0 ; i = e[i].nxt){
        int v = e[i].to;
        if(dfn[v] == 0)
            tarjan(v),low[u] = min(low[u], low[v]);
        else if(vis[v] == true)low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u]){
        sum++;
        while(stk[top + 1] != u){
            scc[stk[top]] = sum;
            vis[stk[top]] = 0;
            top--;
        }
    }
}
int main(){
    n = read();m = read();
    for(int i = 1 ; i <= m ; i ++){
        a = read();x = read();b = read();y = read();
        if(x == 0 && y == 0)add(a + n, b),add(b + n, a);
        if(x == 0 && y == 1)add(a + n, b + n),add(b, a);
        if(x == 1 && y == 0)add(a, b),add(b + n, a + n);
        if(x == 1 && y == 1)add(a, b + n),add(b, a + n);
    }
    for(int i = 1 ; i <= (n << 1) ; i ++)
        if(dfn[i] == 0)tarjan(i);
    for(int i = 1 ; i <= n ; i ++)
        if(scc[i] == scc[i + n]){
            puts("IMPOSSIBLE");return 0;
        }
    puts("POSSIBLE");
    for(int i = 1 ; i <= n ; i ++){
        if(i != 1)putchar(' ');
        if(scc[i] > scc[i + n])putchar('1');
        else putchar('0');
    }
    putchar('\n');return 0;
}
posted @   tsqtsqtsq  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示