学习笔记:2-SAT
2-SAT
什么是 2-SAT?
首先,把「2」和「SAT」拆开。SAT 是 Satisfiability 的缩写,意为可满足性。即一串布尔变量,每个变量只能为真或假。要求对这些变量进行赋值,满足布尔方程。
举个例子:假设一群 在同一个机房里(其实根本不用假设),其中 tsq 为了能够花更多时间在搞颓和玩手机上(是这样的),希望 yb 能满足他的一些要求(例如不能在 tsq 搞颓时打扰他),假设现在有如下要求:
- tsq:我希望 yb 满足下列条件之一:
- 不要带 tsq 玩原神。()
- 要带 tsq 搞卷。()
- 不要带 tsq 打 slay。()
- tsqtsq:我希望 yb 满足下列条件之一:
- 要带 tsqtsq 玩原神。()
- 要带 tsqtsq 搞卷。()
- 不要带 tsqtsq 打 slay。()
- tsqtsqtsq:我希望 yb 满足下列条件之一:
- 要带 tsqtsqtsq 玩原神。()
- 不要带 tsqtsqtsq 搞卷。()
- 要带 tsqtsqtsq 打 slay。()
显然真实的 tsq 是第三种。
我们不妨把三种要求设为 ,,,变量前加 表示「不」,即「假」。上述条件翻译成布尔方程即:。其中, 表示或, 表示与。(就像集合中并集交集一样)
现在要做的是,为 三个变量赋值,满足三位 的要求。
Q: 这可怎么赋值啊?暴力?
A: 对,这是 SAT 问题,已被证明为 NP 完全 的,只能暴力。
Q: 那么 2-SAT 是什么呢?
A: 2-SAT,即每位同学只有两个条件(比如三位 都对要不要 YB 带打 slay 没有要求,这就少了一个条件)不过,仍要使所有同学得到满足。于是,以上布尔方程当中的 , 没了,变成了这个样子:。
怎么求解 2-SAT 问题?
使用强连通分量。 对于每一个变量 ,我们考虑建立两个点 , 分别表示变量 取 true
或者 false
。所以,图的节点的个数是变量个数的两倍。
在存储方式上,我们将第 个变量记为 ,其对应的否定 则为 。对于每一个 的要求 ,我们转换为 。
对于这个式子,可以感性理解为:若 假则 必真,若 假则 必真。
我们考虑这样建图:
原式 | 建图 |
---|---|
建图之后,容易发现同一强连通分量内的变量值一定是相等的。
也就是说,如果 与 在同一强连通分量内部,一定无解。反之,就一定有解了。
但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?
这个很简单,只需要 当 所在的强连通分量的拓扑序在 所在的强连通分量的拓扑序之后取 为真就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 scc[x] < scc[-x]
。
时间复杂度:。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现