2-SAT 问题
引入
有 \(n\) 个布尔变量 \(x_1 \sim x_n\) 另有 \(m\) 个需要满足的条件 每个条件的形式都是 \(「x_i 为 true / false 或 x_j 为 true / false」\) 比如 \(「x_1 为真或 x_3 为假」\)、\(「x_7 为假或 x_2 为假」\)。
求一组解 \(x_i\) 使条件满足
求解
2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足
也就是让这一串布尔变量赋值后满足布尔方程
2 则是指的对于每一个约束涉及两个变量
将每一个条件中的两个变量设为 \(a\) 与 \(b\) 给定的条件为 a 或 b 也就是两个条件只能满足一个
若给出 \(a\) \(1\) \(b\) \(0\) 则要求 \(a\) 为真时 \(b\) 为假 或是 \(a\) 为假时 \(b\) 为真
则可以理解为:若 \(a\) 假 则 \(b\) 必假 若 \(b\) 真 则 \(a\) 必真
只要想办法满足这两个要求就好了
建图: 以 \(1 \sim n\) 表示变量为真 以 \(n + 1 \sim 2n\) 表示变量为假(根据个人习惯)
则上面给出的例子就可以表示为: 由 \(n + x\) 向 \(n + y\) 连边 由 \(y\) 向 \(x\) 建边 (边为有向边)
然后采用 \(tarjan\) 求强联通分量
对于求得的强联通分量中 若是存在 \(x\) \(0\) 与 \(x\) \(1\) 在同一强联通分量中 则该问题无解 否则一定有解
求可行性解:求完强联通分量后 我们拿到了一张拓扑图 在这张图中 若是选择其中的一个点 则这个点所指向的点也必须选择
\(tarjan\) 求得的强联通分量的标号其实就是拓扑逆序 也就是反向的拓扑序 那么一种选择策略就是在每一个变量的真与假中选择其拓扑序较大的点 就可以构造出一组可行解
例题
1. 洛谷 P4782 【模板】2-SAT 问题
题目以及思路如上
code:
/*
Time: 2.1
Worker: Blank_space
Source: P4782 【模板】2-SAT 问题
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
vector <int> e[C << 1];
int n, m, dfn[C << 1], low[C << 1], d[C << 1], top, cnt, st[C << 1], sum;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt; st[++top] = u;
for(int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if(!dfn[v]) {tarjan(v); low[u] = min(low[u], low[v]);}
else if(!d[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
sum++;
while(st[top] != u) d[st[top--]] = sum;
d[st[top--]] = sum;
}
}
/*----------------------------------------函数*/
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
int x = read(), a = read(), y = read(), b = read();
e[x + n * (a ^ 1)].push_back(y + n * (b & 1));//a 假 则 b 真
e[y + n * (b ^ 1)].push_back(x + n * (a & 1));//b 假 则 a 真
}
for(int i = 1; i <= (n << 1); i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= n; i++) if(d[i] == d[i + n]) {puts("IMPOSSIBLE"); return 0;}
puts("POSSIBLE");
for(int i = 1; i <= n; i++) printf("%d ", d[i] > d[i + n]);
return 0;
}
2. 洛谷 P5782 和平委员会
题目:
给定 \(2n\) 个点 点 \(2i - 1\) 与 点 \(2i\) 两点中仅取一个点 给定 \(m\) 个条件 规定给出的两个点不同时取
判断 是否存在一组解满足条件 若存在 输出升序的一组解
思路:
与上面的例题大同小异
\(2i - 1\) 号点 与 \(2i\) 号点 相当于真与假 两者取一
假定给出的不能同时取的两个点为 \(a\) \(b\) (此处假定 \(a\) \(b\) 为奇数 偶数同理)
若是不取 \(a\) 则必须取 \(a + 1\) 我们建一条由 \(b\) 指向 \(a + 1\) 的边(此处只关心 \(a\) 的奇偶性)
若是不取 \(b\) 则必须取 \(b + 1\) 我们建一条由 \(a\) 指向 \(b + 1\) 的边(此处只关心 \(b\) 的奇偶性)
\(tarjan\) 求强联通分量 按照强联通分量的标号取点即可
code:
/*
Time: 2.1
Worker: Blank_space
Source: #10097. 「一本通 3.5 练习 5」和平委员会
2-SAT 问题 板子
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
vector <int> e[A << 1];
int n, m, dfn[A << 1], low[A << 1], st[A << 1], top, cnt, sum, d[A << 1];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt; st[++top] = u;
for(int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if(!dfn[v]) {tarjan(v); low[u] = min(low[u], low[v]);}
else if(!d[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
sum++;
while(st[top] != u) d[st[top--]] = sum;
d[st[top--]] = sum;
}
}
/*----------------------------------------函数*/
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
int x = read(), y = read();
if(y & 1) e[x].push_back(y + 1); else e[x].push_back(y - 1);
if(x & 1) e[y].push_back(x + 1); else e[y].push_back(x - 1);
}
for(int i = 1; i <= (n << 1); i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= (n << 1); i += 2) if(d[i] == d[i + 1]) {puts("NIE"); return 0;}
for(int i = 1; i <= (n << 1); i += 2) printf("%d\n", d[i] < d[i + 1] ? i : i + 1);
return 0;
}
后面如果在做这一类的题目会再补充 先写到这