2-SAT 学习笔记
前言
3.13 这2-SAT是训练最后一天学的,补笔记补的有点晚了,(训练完后天就开学了鸽了10天)
举个例子
分配组长
\(\quad\)现在,我们班的小组要选组长(组长可以不止一个,学习组长,体育组长,奥赛组长...)了
假如现在我们组有5个人,有的人选择了弃票所以只有三个人选择了投票,投票规则是每个人写出自己所希望当组长的人或自己不希望当组长的人(仅限俩要求)
由于班主任倡导学生自己的事情自己办只要不闹出矛盾就行,所以为了不让同学们对组长产生质疑,我们要求必须满足每一个的至少一个要求,这样就可以让每个人都信服
每个人的要求如下:
-
一号投票者
-
我希望让2号当组长
-
我希望让1号当组长
-
-
二号投票者
-
我不希望让3号当组长
-
我不希望让2号当组长
-
-
三号投票者
-
我希望让4号当组长
-
我不希望让5号当组长
-
你的工作是给这五个人授予组长的职位,如果某人的布尔变量为 \(true\) 表示赋予这个人组长的职务,\(false\) 表示组员
如何授予职业呢?这对于sfg来说是个麻烦的工作呢,但是聪明绝顶的你一下就看出来了:哇,这不就是2-SAT(Satisfiability 可满足性)问题吗 秒了
\(\quad\)别着急,我们不妨将每个人的要求变成一个布尔(布尔值:只有两种值,是(true/1)非(false/0))方程:想让1当组长记为 \(a\) , 不想让1号当组长记为 \(\lnot a\) 同样的想让2当组长记为 \(b\) , 不想让2当记为 \(\lnot b\) 后面的以此类推c, d, e
那么我们是不是可以将每一个人的要求记为一个式子?
注:\(\lor\) 表示||或 \(\land\) 表示&&与 \(\lnot\) 表示!非 或者叫 否
- 一号投票者: \(b \lor a\)
- 二号投票者: \(\lnot c \lor \lnot b\)
- 三号投票者: \(d \lor \lnot e\)
\(\quad\)+ 压缩成一个式子:\((b \lor a) \land (\lnot c \lor \lnot b) \land (d \lor \lnot e)\) 满足此式子即可符合题意
这时候聪明的你就要说了:别废话了,讲正题!!!
2-SAT
\(\quad\)根据上面的例子想必你已经知道什么是2-SAT问题了吧?什么?不知道,没关系,简单来讲:2-SAT就是给定你n个布尔方程,每个布尔方程和只和两个变量相关 如 \(a \lor b\) 表示为a,b至少要满足一个,然后判断是否存在可行解,若存在输出一种方案(一般的题目都是这样)
\(\quad\)好了,让我们用2-SAT来解决问题吧!
我们来设两个点:\(i\)表示\(i\)布尔值为\(true\),\(i+n\)表示\(i\)布尔值为\(false\)
对于要求 例如:\(a \lor b\) 转化为边 __ \(\lnot a \rightarrow b \land \lnot b \rightarrow a\) __ 我们可以将其理解为 【若a为假,则b为真 且 若b为假,则a为真】 那么聪明绝顶的你可以列出一个表格:
| 原式 | 建边 |
|---|---|
| \(a \lor b\) | \(\lnot a \rightarrow b \land \lnot b \rightarrow a\) |
| \(\lnot a \lor b\) | \(a \rightarrow b \land \lnot b \rightarrow \lnot a\) |
| \(\lnot a \lor \lnot b\) | \(a \rightarrow \lnot b \land b \rightarrow \lnot a\) |
按照我们上面的例子,我们可以画出一个图:

\(\quad\)要请出我们的强大的tarjan算法了!(没学过tarjan去学一下)

\(\quad\)如图,我们很容易想到,如果在一个连通分量内,所有点要么都为真,要么都为假,那么如果一个连通分量内既有 \(u\) 节点又有 \(\lnot u\) 节点,二者不可能同时为真或假,这样就无解
输出解的时候,如果变量 \(x\) 的拓扑序在 \(\neg x\) 之后,那么取 \(x\) 值为真,在tarjan中,就是\(x\) 的scc编号在 \(\neg x\) 之后
由于我们只是吧图遍历了一遍,所以时间复杂度是 \(O(N)\) 可以看到十分的优秀,这样你就成功解决2-SAT问题!(sfg要是有你一半聪明就好了)
【模板】2-SAT
非常板的题(因为就叫板子题)根据上方说法建边跑tarjan即可
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e6 + 10;
int n, m;
int dfn[N], low[N], tot;
int stk[N], tt = 0;
bool st[N];
int id[N], cnt;
vector<int> e[N];
inline void tarjan(int u){
dfn[u] = low[u] = ++ tot;
stk[ ++ tt] = u;
st[u] = true;
for(int j : e[u]){
if(!dfn[j]){
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if(st[j]){
low[u] = min(low[u], dfn[j]);
}
}
if(dfn[u] == low[u]){
int y;
cnt ++;
do{
y = stk[tt --];
st[y] = false;
id[y] = cnt;
}while(u != y);
}
}
int main(){
cin>>n>>m;
for(int i = 1 ; i <= m ; i ++){
int a, b, va, vb;
cin>>a>>va>>b>>vb;
if(va && vb){
e[a + n].push_back(b);
e[b + n].push_back(a);
}
else if(!va && vb){
e[b + n].push_back(a + n);
e[a].push_back(b);
}
else if(va && !vb){
e[a + n].push_back(b + n);
e[b].push_back(a);
}
else if(!va && !vb){
e[a].push_back(b + n);
e[b].push_back(a + n);
}
}
for(int i = 1 ; i <= (n << 1) ; 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 ++){
cout<<(id[i] < id[i + n])<<' ';
}
return 0;
}
P6965 [NEERC 2016] Binary Code
\(quad\)

浙公网安备 33010602011771号