Skip to content

Commit

Permalink
PUSH NOTE : 2-SAT.md
Browse files Browse the repository at this point in the history
  • Loading branch information
hyj0824 committed Dec 15, 2024
1 parent 3ae2a3b commit 6fba360
Showing 1 changed file with 211 additions and 0 deletions.
211 changes: 211 additions & 0 deletions docs/数学/图论/2-SAT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
---
tags:
- 2-SAT
- tarjan
- SCC
- 图论
date: 2023-06-27
publish: true
---

前置知识:Tarjan 缩点,逻辑运算法则(且对或的分配率、摩根率)

SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 k>2 时该问题为 NP 完全的。**只有2-SAT有多项式复杂度。**

## 简介

2-SAT,就是 $n$ 个集合,每个集合有2个元素,每个集合只能选取其中的一个元素(不能不选),又已知若干个 $\langle a,b \rangle$,表示 $a$ 与 $b$ 矛盾(其中 $a$ 与 $b$ 属于不同的集合)。

判断能否一共选 $n$ 个两两不矛盾的元素满足所有条件。显然可能有多种选择方案,一般题中只需要求出一种即可。

等价于:[P4782 【模板】2-SAT - 洛谷 | 计算机科学教育新生态](https://www.luogu.com.cn/problem/P4782)

有 $n$ 个布尔变量 $x_1\sim x_n$,另有若干个需要满足的条件,每个条件的形式都是

**「$x_i$ 为 `true` / `false` 或 $x_j$ 为 `true` / `false`**

比如 「$x_1$ 为真或 $x_3$ 为假」、「$x_7$ 为假或 $x_2$ 为假」

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

> [!hint] 为什么等价?
> 这里的 $n$ 个变量就是上面的集合,均为 $\{false,true\}$。不妨将第 $i$ 个集合写成 $\{F_i=false,T_i=true\}$,分别表示 $i=false/true$,则条件「$x_1$ 为真或 $x_3$ 为假」可以看作 $F_1$ 和 $T_3$ 有矛盾,不能同时取。
## 分析建模

我们似乎隐隐感觉到要把每个变量(集合)拆成两个元素来解决问题。用图论尝试建模这个条件,将有向边看成「已知某个变量的取值,一定能够推出另一个变量的值」的推导关系。

若设$x_1=F_1=false$,要满足条件,则$x_3$只能取$F_3=false$,连边 $F_1\rightarrow F_3$;
同样设$x_3=T_3=true$,要满足条件,则$x_1$只能取$T_1=true$,连边 $T_3\rightarrow T_1$。

而设$x_1=T_1=true$,满足条件时,$x_3$两个值都能取,是要连两条边吗?

不!!!!!!

边的定义是「已知某个变量的取值,**一定能够推出另一个变量的值**」的推导关系。虽然是推导关系,但这是不确定的关系,不能连边。

> 注:2-SAT 并不只有「或」的约束,所有常见约束处理在文末。
## 转化成图论,然后呢?

这样就转化成了一个图论问题。对于一个变量$x_i$,若$T_i$经过一条链的推导到$F_i$,则说明$x_i=F_i=false$。图上拓扑序大的点便是变量经过推导后最终的取值。

但是这可能会有环,并且当一个变量的真和假两个点在在一个环里时,是真假互推,同时取真假的叠加态,显然是错误的。

因此我们跑一遍 Tarjan SCC 判断是否有一个变量的两个点在同一个 SCC 中,若有则输出不可能,否则输出方案。

方案?我们不用再跑一边toposort了,缩点的scc_id是反着的拓扑序(dfs越先搜到说明越深),详见代码。

```cpp
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using LL = long long;

const int maxn = 4e6+10; //二倍点 四倍边

// 2-sat

struct Edge {
int to,pre;
} e[maxn*2];
int head[maxn],tot;
void addedge(int u,int v) {
e[++tot] = {v,head[u]}, head[u]=tot;
}

int dfn[maxn],low[maxn],st[maxn],top,id;
bool in_st[maxn];
int n,m;
int scc_id,scc[maxn]; //记录染色

void tarjan(int now) {
dfn[now]=low[now]=++id;
st[++top]=now;
in_st[now]=1;
for(int p=head[now]; p; p=e[p].pre) {
int i = e[p].to;
if(!dfn[i]) {
tarjan(i);
low[now] = std::min(low[now],low[i]);
} else if(in_st[i]) {
low[now] = std::min(low[now],dfn[i]);
}
}
if(dfn[now]==low[now]){
scc_id++;
while(st[top]!=now){
scc[st[top]]=scc_id;
in_st[st[top]]=0;
top--;
}
scc[st[top]]=scc_id;
in_st[st[top]]=0;
top--;
// 弹出now
}
}

int main() {
cin >> n >> m;
for(int i=1; i<=m; i++) {
int a,b,va,vb;
cin >> a >> va >> b >> vb;
// 反向思考 一个条件不成立则另一个条件必须成立
// 真:1~n 假:n+1~2n
if(va && vb) {
// a, b 都真,-a -> b, -b -> a
addedge(a+n,b),addedge(b+n,a);
} else if(va && !vb) {
// a 真 b 假,-a -> -b, b -> a
addedge(a+n,b+n),addedge(b,a);
} else if(!va && vb) {
// a 假 b 真,a -> b, -b -> -a
addedge(a,b),addedge(b+n,a+n);
} else {
// a, b 都假,a -> -b, b -> -a
addedge(a,b+n),addedge(b,a+n);
}
// addedge(a+va*n,b+(vb^1)*n);
// addedge(b+vb*n,a+(va^1)*n);
}
for(int i=1; i<=2*n; i++) {
if(!dfn[i]) tarjan(i);
}
// 判断无解 自己和反点没有在一个scc(不取相同值)
for(int i=1;i<=n;i++){
if(scc[i]==scc[i+n]){
cout << "IMPOSSIBLE";
return 0;
}
}
cout << "POSSIBLE\n";
for(int i=1;i<=n;i++){
if(scc[i+n] < scc[i]) cout << "0 "; // 假的点的编号更小,取假
else cout << "1 ";
}
return 0;
}
```
---
建边也可以用位运算,大概就是:
两个元素相互矛盾,选一个则必定选另一个的补;
两个元素用或连接,不选一个(选了这个的补)则必须选另一个。
两个相互矛盾的条件,一个成立则另一个必定不成立;
两个用或连接的条件,一个不成立则另一个必须成立。
其实,2-SAT之所以有多项式复杂度,根源就在这里——必定能推断的原因是每个集合都有且仅有两个元素可选,不能不选,一个不可选,则只能选另一个了。
```cpp
if(A=='h') x=0; else x=1;
if(B=='h') y=0; else y=1;
// 多个条件:Aa 或 Bb
// `h汉`在1~n; `m满`在n+1~2n
// 若不选Aa而选择了Ba 则Ba->Bb
add(a+(x^1)*n,b+y*n);
// 若不选Bb而选择了Ab 则Ab->Aa
add(b+(y^1)*n,a+x*n);
```

https://www.luogu.com.cn/problem/P4171

---

还有一个例子,差不多。

邀请人来吃喜酒,夫妻二人**必须去一个**(不是必须去就不是2-SAT了),然而某些人之间有矛盾(比如 A 先生与 B 女士有矛盾,C 女士不想和 D 先生在一起),那么我们要确定能否避免来人之间有矛盾?方案呢?

## 常见约束转化

我们令一条**有向边**的意义:$x\rightarrow y$ 表示如果选择了 $x$ 就必须选 $y$。考虑钦定一个变量取反,若要满足约束,另一个**一定**会取什么。

可以举一些简单的例子来总结下连边的规律(用 $i'$ 表示 $i$ 的反面):

1. $x,y$ 必须同时选,X且Y:$x\rightarrow y,y\rightarrow x$
2. $x,y$ 都不能选,非X且非Y:$x'\rightarrow y',y'\rightarrow x'$

“且”表达了强烈的推导关系,下面是或:

1. $x,y$ 不能同时选,$\lnot(X \wedge Y) \Rightarrow \lnot X \vee \lnot Y$:$x\rightarrow y',y\rightarrow x'$
2. $x,y$ 中只选一个,不能不选,$(\lnot X \wedge Y) \vee (X \wedge \lnot Y) \Rightarrow (X \vee Y) \wedge (\lnot X \vee \lnot Y)$:$x'\rightarrow y,y\rightarrow x',x\rightarrow y',y'\rightarrow x$(这个先双重取反,用摩根率,分配率乘开,再摩根回来)

“或”的约束不强,有时候可以分类讨论。最后是必须选某一个:

1. $x$ 必须选:$x'\rightarrow x$
2. $x'$ 必须选:$x\rightarrow x'$

如果是复杂的多个条件并列的时候,进行逻辑运算化成多个二元条件的且,比如此题(4-SAT转2-SAT):[D. 滈葕](https://tg.hszxoj.com/contest/849/problem/4)

## 参考文献:

[题解 P4782 【模板】2-SAT 问题 - 暗ざ之殇 的博客 - 洛谷博客](https://www.luogu.com.cn/blog/xcg--123/ti-xie-p4782-mu-ban-2-sat-wen-ti)

[LibreOJ](https://loj.ac/p/3629) Sol:[【2021集训队出题】序列 - 冰雾 - 博客园](https://www.cnblogs.com/FrozenFoggy/p/15824031.html)

[2-SAT超入门讲解 - 空気力学の詩 - 博客园](https://www.cnblogs.com/cjjsb/p/9771868.html)

[逻辑运算定律 - 知乎](https://zhuanlan.zhihu.com/p/65877185)

0 comments on commit 6fba360

Please sign in to comment.