2-SAT适定性问题与逻辑图
逻辑图
逻辑图是一种有向图,图中每一个节点代表了一个命题,如果命题 a a a是命题 b b b的充分不必要条件,那么在 a a a和 b b b之间就存在一条有向边 a → b a \to b a→b。如果 a a a和 b b b互为充要条件,那么 a a a和 b b b之间存在双向边。
构建逻辑图可以为我们通过图论解决命题之间的关系等问题,例如2-SAT问题。
2-SAT适定性问题
SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 k > 2 k > 2 k>2 时该问题为 NP 完全的。所以我们只研究 k = 2 k=2 k=2 的情况。
给定一个逻辑表达式:
L = ( a 1 ∨ b 1 ) ∧ ( a 2 ∨ b 2 ) ∧ … ∧ ( a n ∨ b n ) L = (a_1 \vee b_1) \wedge (a_2 \vee b_2) \wedge \ldots \wedge (a_n \vee b_n) L=(a1∨b1)∧(a2∨b2)∧…∧(an∨bn)
再给定一组未知布尔变量组 X = ( x 1 , x 2 , x 3 , … , x m ) X = (x_1,x_2,x_3,\ldots,x_m) X=(x1,x2,x3,…,xm),其中 a i a_i ai和 b i b_i bi要么等于 x j x_j xj要么等于 ¬ x j \neg x_j ¬xj。
求解是否有可行的 X X X的解集,或者报告无解。上述问题称为2-SAT问题, L L L为2-SAT逻辑表达式。
求解
我们注意到,如果存在条件 ( a i ∨ b i ) (a_i \vee b_i) (ai∨bi),那么有 ¬ a i → b i \neg a_i \to b_i ¬ai→bi和 ¬ b i → a i \neg b_i \to a_i ¬bi→ai。我们根据这个关系建立逻辑图。
例如:
L 2 = ( x 2 ∨ ¬ x 1 ) ∧ ( ¬ x 1 ∨ ¬ x 2 ) ∧ ( x 1 ∨ x 3 ) ∧ ( ¬ x 2 ∨ ¬ x 3 ) ∧ ( ¬ x 1 ∨ x 4 ) L 2 = ( x 1 ∨ x 2 ) ∧ ( x 1 ∨ ¬ x 2 ) ∧ ( ¬ x 1 ∨ x 3 ) ∧ ( ¬ x 1 ∨ ¬ x 3 ) L_2 = (x_2 \vee \neg x_1) \wedge (\neg x_1 \vee \neg x_2) \wedge (x_1 \vee x_3) \wedge (\neg x_2 \vee \neg x_3) \wedge (\neg x_1 \vee x_4) \\ L_2 = (x_1 \vee x_2) \wedge (x_1 \vee \neg x_2) \wedge (\neg x_1 \vee x_3) \wedge (\neg x_1 \vee \neg x_3) L2=(x2∨¬x1)∧(¬x1∨¬x2)∧(x1∨x3)∧(¬x2∨¬x3)∧(¬x1∨x4)L2=(x1∨x2)∧(x1∨¬x2)∧(¬x1∨x3)∧(¬x1∨¬x3)
则有 L 1 L_1 L1逻辑图:
L 2 L_2 L2逻辑图:
其解存在的充要条件为: x i x_i xi和 ¬ x i \neg x_i ¬xi不在同一个强连通分量中。如果存在 x i x_i xi和 ¬ x i \neg x_i ¬xi在同一强连通分量,那么这两者不可能同时为 True \text{True} True,故解自然不存在。
L 1 L_1 L1满足这个条件,故 L 1 L_1 L1的解存在。 L 2 L_2 L2不满足,则 L 2 L_2 L2的解不存在。
判断解存不存在之后,我们要构造一个可行解。
我们发现,如果缩点之后的分量图中存在 ¬ x i → x i \neg x_i \to x_i ¬xi→xi,那么 x i x_i xi一定为 True \text{True} True,反之存在 x i → ¬ x i x_i \to \neg x_i xi→¬xi,那么 x i x_i xi一定为 False \text{False} False。那么我们就可以选择 x i x_i xi和 ¬ x i \neg x_i ¬xi谁的拓扑序靠后,谁的就为 True \text{True} True。这样就能构造出一组可行解。
例题
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
typedef long long ll;
using namespace std;
int n, m;
#define RS(x) (x <= n ? n + x : x - n)
struct Edge
{
int nxt;
int to;
} e[2000005];
int head[2000005];
int tot = 0;
void add(int u, int v)
{
tot++;
e[tot].to = v;
e[tot].nxt = head[u];
head[u] = tot;
}
int dfn[2000005];
int low[2000005];
bool inStack[2000005];
stack<int> sta;
int ti = 0;
int topo[2000005];
int id = 0;
int val[2000005];
void tarjan(int u)
{
low[u] = dfn[u] = ++ti;
inStack[u] = true;
sta.push(u);
for (int ne = head[u]; ne; ne = e[ne].nxt)
{
int to = e[ne].to;
if (dfn[to] == 0)
{
tarjan(to);
low[u] = min(low[u], low[to]);
}
else if (inStack[to])
{
low[u] = min(low[u], dfn[to]);
}
}
if (dfn[u] == low[u])
{
id++;
int curr;
do
{
curr = sta.top();
sta.pop();
inStack[curr] = false;
topo[curr] = id;
} while (curr != u);
}
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++)
{
int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);
if (b == 0)
a = RS(a);
if (d == 0)
c = RS(c);
add(RS(a), c);
add(RS(c), a);
}
for (int i = 1; i <= 2 * n; i++)
{
if (dfn[i] == 0)
{
tarjan(i);
}
}
for (int i = 1; i <= n; i++)
{
if (topo[i] == topo[RS(i)])
{
printf("IMPOSSIBLE\n");
return 0;
}
else if (topo[i] > topo[RS(i)])
{
val[i] = 0;
}
else if (topo[i] < topo[RS(i)])
{
val[i] = 1;
}
}
printf("POSSIBLE\n");
for (int i = 1; i <= n; i++)
{
printf("%d ", val[i]);
}
return 0;
}