SAT is short for the satisfiability problem. Its general form is the k-satisfiability problem, abbreviated k-SAT. When , the problem is NP-complete, so we study only the case .
Definition
In simple terms, 2-SAT gives Boolean equations, each involving two variables, such as , meaning that at least one of the variables is satisfied. We then determine whether a feasible assignment exists. There may be multiple feasible assignments; in most problems, finding any one is enough. In addition, means that is negated.
Solution Idea
Luogu P4782 “2-SAT Template”
There are Boolean variables and conditions to satisfy. Each condition has the form “ is true/false or is true/false.” For example, “ is true or is false” and “ is false or is false.”
The goal of 2-SAT is to assign a value to each variable so that all conditions are satisfied.
Represent the problem above with Boolean equations. Let mean that is true ( means that is false). If someone's requirements are and , that is, (at least one of the variables is satisfied), construct a directed graph for these relationships. Represent whether is true or false by vertices of the graph; means that if is false, then is necessarily true. Similarly, if is false, then is necessarily true. After constructing the graph, we can solve 2-SAT by contracting strongly connected components.
| Original clause | Implication graph |
|---|---|
| and | |
| and | |
| and |
Many 2-SAT problems require finding relationships such as: if is false, then is true.
Solving
Consider what it means for two vertices to lie in the same strongly connected component. From the logical meaning of the edges above, if two vertices lie in the same SCC, then the conditions they represent are either both satisfied or both unsatisfied.
After constructing the graph, use Tarjan algorithm for finding SCCs to find SCCs. For every Boolean variable , determine whether the vertex representing being true and the vertex representing being false lie in the same SCC (one condition cannot be both satisfied and unsatisfied, or be both unsatisfied and not unsatisfied). If so, output that there is no solution; otherwise, a solution exists.
To output an assignment, determine each variable's value from its topological order in the graph. If the topological order of variable comes after that of , set to true. After applying Tarjan's SCC contraction, this means that when the SCC containing has a number before that of , set to true. Tarjan's algorithm uses a stack when finding SCCs: a component with a larger topological order is visited later by Tarjan, popped from the stack and contracted earlier, and therefore receives a smaller component number. Thus, the SCC numbers produced by Tarjan correspond to the reverse topological order.
The algorithm traverses the entire graph once. Since and are of the same order, computing the answer takes time, so the total complexity is .
Implementation
#include <algorithm>
#include <cstdio>
#include <stack>
using namespace std;
const int N = 2e6 + 2;
int n, m, dfn[N], low[N], t, tot, head[N], a[N];
bool vis[N];
stack<int> s;
struct node {
int to, Next;
} e[N];
void adde(int u, int v) {
e[++tot].to = v;
e[tot].Next = head[u];
head[u] = tot;
}
void Tarjan(int u) {
dfn[u] = low[u] = ++t;
s.push(u);
vis[u] = 1;
for (int i = head[u]; i; i = e[i].Next) {
int v = e[i].to;
if (!dfn[v]) {
Tarjan(v);
low[u] = min(low[u], low[v]);
} else if (vis[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
int cur;
++tot;
do {
cur = s.top();
s.pop();
vis[cur] = 0;
a[cur] = tot;
} while (cur != u);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1, I, J, A, B; i <= m; i++) {
scanf("%d%d%d%d", &I, &A, &J, &B);
adde(A ? I + n : I, B ? J : J + n);
adde(B ? J + n : J, A ? I : I + n);
}
tot = 0;
for (int i = 1; i <= (n << 1); i++)
if (!dfn[i]) Tarjan(i);
for (int i = 1; i <= n; i++) {
if (a[i] == a[i + n]) {
printf("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for (int i = 1; i <= n; i++)
printf("%c%c", a[i] < a[i + n] ? '1' : '0', " \n"[i == n]);
return 0;
}
Examples
Example 1
HDU3062 Party
There are couples invited to a party. Because of venue constraints, only one person from each couple can attend. Among the people, some pairs have serious conflicts (of course, spouses do not conflict), and two people with a conflict cannot attend together. Is it possible for people to attend simultaneously?
Following the analysis above, if the husband from conflicts with the wife from , add an edge from the husband of to the husband of , and from the wife of to the wife of ; then contract the SCCs and assign components to determine the answer.
Reference Code
#include <algorithm>
#include <cstring>
#include <iostream>
constexpr int MAXN = 2018;
constexpr int MAXM = 4000400;
using namespace std;
int Index, instack[MAXN], DFN[MAXN], LOW[MAXN];
int tot, color[MAXN];
int numedge, head[MAXN];
struct Edge {
int nxt, to;
} edge[MAXM];
int sta[MAXN], top;
int n, m;
void add(int x, int y) {
edge[++numedge].to = y;
edge[numedge].nxt = head[x];
head[x] = numedge;
}
void tarjan(int x) { // If SCC contraction is unclear, see the linked strongly connected components page above.
sta[++top] = x;
instack[x] = 1;
DFN[x] = LOW[x] = ++Index;
for (int i = head[x]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (!DFN[v]) {
tarjan(v);
LOW[x] = min(LOW[x], LOW[v]);
} else if (instack[v])
LOW[x] = min(LOW[x], DFN[v]);
}
if (DFN[x] == LOW[x]) {
tot++;
do {
color[sta[top]] = tot; // Assign the component
instack[sta[top]] = 0;
} while (sta[top--] != x);
}
}
bool solve() {
for (int i = 0; i < 2 * n; i++)
if (!DFN[i]) tarjan(i);
for (int i = 0; i < 2 * n; i += 2)
if (color[i] == color[i + 1]) return false;
return true;
}
void init() {
top = 0;
tot = 0;
Index = 0;
numedge = 0;
memset(sta, 0, sizeof(sta));
memset(DFN, 0, sizeof(DFN));
memset(instack, 0, sizeof(instack));
memset(LOW, 0, sizeof(LOW));
memset(color, 0, sizeof(color));
memset(head, 0, sizeof(head));
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
while (cin >> n >> m) {
init();
for (int i = 1; i <= m; i++) {
int a1, a2, c1, c2;
cin >> a1 >> a2 >> c1 >> c2;
add(2 * a1 + c1, 2 * a2 + 1 - c2);
// For the i-th couple, 2i+1 denotes the husband and 2i denotes the wife.
add(2 * a2 + c2, 2 * a1 + 1 - c1);
}
if (solve())
cout << "YES\n";
else
cout << "NO\n";
}
return 0;
}
Example 2
2018–2019 ACM-ICPC Asia Seoul Regional K TV Show Game
There are lamps, each red or blue, but their initial colors are unknown. There are people; each chooses three lamps and guesses their colors. A person wins a prize if at least two guesses are correct. Determine whether there is a coloring in which everyone wins, and if so, output one such coloring.
According to Yu Wu — Solving 2-SAT Problems by Symmetry, to output a feasible solution to a 2-SAT problem, choose and delete vertices from the bottom up in the DAG obtained after SCC contraction.
In practice, this can be implemented by constructing the reverse graph of the DAG and performing a topological sort on it. Alternatively, after Tarjan's contraction, use the fact that a smaller component number means a vertex is closer to a leaf, and prioritize vertices with smaller component numbers.
The second implementation is given below.
Reference Code
#include <algorithm>
#include <iostream>
using namespace std;
constexpr int MAXN = 1e4 + 5;
constexpr int MAXK = 5005;
int n, k;
int id[MAXN][5];
char s[MAXN][5][5], ans[MAXK];
bool vis[MAXN];
struct Edge {
int v, nxt;
} e[MAXN * 100];
int head[MAXN], tot = 1;
void addedge(int u, int v) {
e[tot].v = v;
e[tot].nxt = head[u];
head[u] = tot++;
}
int dfn[MAXN], low[MAXN], color[MAXN], stk[MAXN], ins[MAXN], top, dfs_clock, c;
void tarjan(int x) { // Tarjan's algorithm for SCCs
stk[++top] = x;
ins[x] = 1;
dfn[x] = low[x] = ++dfs_clock;
for (int i = head[x]; i; i = e[i].nxt) {
int v = e[i].v;
if (!dfn[v]) {
tarjan(v);
low[x] = min(low[x], low[v]);
} else if (ins[v])
low[x] = min(low[x], dfn[v]);
}
if (dfn[x] == low[x]) {
c++;
do {
color[stk[top]] = c;
ins[stk[top]] = 0;
} while (stk[top--] != x);
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> k >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 3; j++) cin >> id[i][j] >> s[i][j];
for (int j = 1; j <= 3; j++) {
for (int k = 1; k <= 3; k++) {
if (j == k) continue;
int u = 2 * id[i][j] - (s[i][j][0] == 'B');
int v = 2 * id[i][k] - (s[i][k][0] == 'R');
addedge(u, v);
}
}
}
for (int i = 1; i <= 2 * k; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= 2 * k; i += 2)
if (color[i] == color[i + 1]) {
cout << "-1\n";
return 0;
}
for (int i = 1; i <= 2 * k; i += 2) {
int f1 = color[i], f2 = color[i + 1];
if (vis[f1]) {
ans[(i + 1) >> 1] = 'R';
continue;
}
if (vis[f2]) {
ans[(i + 1) >> 1] = 'B';
continue;
}
if (f1 < f2) {
vis[f1] = true;
ans[(i + 1) >> 1] = 'R';
} else {
vis[f2] = true;
ans[(i + 1) >> 1] = 'B';
}
}
ans[k + 1] = 0;
cout << (ans + 1) << '\n';
return 0;
}