【2-sat】2-sat算法内容及真题
A.2-sat简介
2-sat算法可以求解给定推出关系下的一种合法情况。题目中重常常,给定一些布尔变量A、B、C、D…,再给出一系列形如 B ⟶ A , C ⟶ ¬ D B \longrightarrow A , C \longrightarrow \neg D B⟶A,C⟶¬D的推出关系,询问使得所有推出关系都成立的一组布尔变量取值。
B.2-sat算法内容
1.无解情况
一个变量要么为真,要么为假,不能同时为真又为假。
如果 A ⟶ ¬ A A \longrightarrow \neg A A⟶¬A,那该变量不能取值为真,因为,为真就触发矛盾
如果 ¬ A ⟶ A \neg A \longrightarrow A ¬A⟶A,那该变量不能取值为假,因为,为假就触发矛盾
所以,如果 A ⟶ ¬ A A \longrightarrow \neg A A⟶¬A, ¬ A ⟶ A \neg A \longrightarrow A ¬A⟶A,就找不到合法解。
其余情况有解。
2.构造合法解
尝试贪心地构造一组合法解。
观察这样一组情况
A ⟶ ¬ B ⟶ C ⟶ ¬ A ⟶ D ⟶ B A \longrightarrow \neg B\longrightarrow C\longrightarrow \neg A\longrightarrow D \longrightarrow B A⟶¬B⟶C⟶¬A⟶D⟶B
由于关系的传递性,
- 如果A为真,后面5个变量都要合法,即 B为假,C为真,A为假,D为真,B为真。不难发现,A为真且为假,B为真且为假,矛盾。
- 如果A为假,只需最后3个变量合法,即D为真,B为真。
得出结论,对于变量A,选择拓扑序靠后一个,更优。具体一些,严格优于另一个。
推广到一般,对于任意变量X,选择拓扑序靠后的取值为答案中的值。
3.算法实现
- 首先,需要建图。假设共有n个变量,点 1,2,3…n表示变量为真情况,点 1 + n,2 + n,…n+n表示变量为假的情况。对于第 i 个变量,点 i 表示该变量为真, 点 i + n表示该变量为假。往每个点中存入由该点能推出的点,例如,
- 变量 n 为真 推出 变量 1 为真,则往点n中存入1;
- 变量 n 为真 推出 变量 1 为假,则往点n中存入 1 + n;
- 变量 n 为假 推出 变量 1 为假,则往点n + n中存入 1
- 判断无解情况,对整个图求强联通分量,如果 i 与 i + n在一个强联通分量内部,则存在上文所说的 “ A ⟶ ¬ A A \longrightarrow \neg A A⟶¬A, ¬ A ⟶ A \neg A \longrightarrow A ¬A⟶A”,则无解。
- 如果有解,对每个变量,输出拓扑序靠后的一个,即强联通分量编号小的一个(tarjan算法的性质)。
\\tarjan
const int N = 1e6 + 10;
int dfn[N],low[N],cnt;
stack<int> stk;
bool ins[N];
int id[N],scc_cnt,sz[N];
vector<int> g[N];
int n,m;void tarjan(int u)
{dfn[u] = low[u] = cnt ++;stk.push(u);ins[u] = 1;for(auto j : g[u]){if(!dfn[j]){tarjan(j);low[u] = min(low[u],low[j]);}else if(ins[j]){low[u] = min(low[u],dfn[j]);}}if(dfn[u] == low[u]){int y;++scc_cnt;do{y = stk.top();stk.pop();ins[y] = 0;id[y] = scc_cnt;sz[scc_cnt] ++;}while(y != u);}
}
\\判断有无解for(int i = 1; i <= 2*n; i ++){if(!dfn[i]) tarjan(i);}for(int i = 1; i <= n; i ++){if(id[i] == id[i+n]){cout << "NO\n";return;}}
\\存储答案vector<int> ans;for(int i = 1; i <= n; i ++){if(id[i] < id[i + n])ans.push_back(i);}
例题 P4782 【模板】2-SAT
题目描述
有 n n n 个布尔变量 x 1 ∼ x n x_1\sim x_n x1∼xn,另有 m m m 个需要满足的条件,每个条件的形式都是 「 x i x_i xi 为 true
/ false
或 x j x_j xj 为 true
/ false
」。比如 「 x 1 x_1 x1 为真或 x 3 x_3 x3 为假」、「 x 7 x_7 x7 为假或 x 2 x_2 x2 为假」。
2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入格式
第一行两个整数 n n n 和 m m m,意义如题面所述。
接下来 m m m 行每行 4 4 4 个整数 i i i, a a a, j j j, b b b,表示 「 x i x_i xi 为 a a a 或 x j x_j xj 为 b b b」( a , b ∈ { 0 , 1 } a, b\in \{0,1\} a,b∈{0,1})
输出格式
如无解,输出 IMPOSSIBLE
;否则输出 POSSIBLE
。
下一行 n n n 个整数 x 1 ∼ x n x_1\sim x_n x1∼xn( x i ∈ { 0 , 1 } x_i\in\{0,1\} xi∈{0,1}),表示构造出的解。
输入输出样例 #1
输入 #1
3 1
1 1 3 0
输出 #1
POSSIBLE
0 0 0
说明/提示
1 ≤ n , m ≤ 1 0 6 1\leq n, m\leq 10^6 1≤n,m≤106 , 前 3 3 3 个点卡小错误,后面 5 5 5 个点卡效率。
由于数据随机生成,可能会含有( 10 0 10 0)之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里。
思路
- A ∨ B ⟺ ¬ A ⟶ B , ¬ B ⟶ A A\vee B \Longleftrightarrow \neg A\longrightarrow B,\neg B\longrightarrow A A∨B⟺¬A⟶B,¬B⟶A
练习
【2018ICPC首尔区域赛】
【2023ICPC济南区域赛】
【2023ICPC网络预选赛(2)】