2-SAT

SAT is short for the satisfiability problem. Its general form is the k-satisfiability problem, abbreviated k-SAT. When k>2k>2, the problem is NP-complete, so we study only the case k=2k=2.

Definition

In simple terms, 2-SAT gives nn Boolean equations, each involving two variables, such as a∨ba \vee b, meaning that at least one of the variables a,ba, b 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, ¬a\neg a means that aa is negated.

Solution Idea

Luogu P4782 “2-SAT Template”

There are nn Boolean variables x1∼xnx_1\sim x_n and mm conditions to satisfy. Each condition has the form “xix_i is true/false or xjx_j is true/false.” For example, “x1x_1 is true or x3x_3 is false” and “x7x_7 is false or x2x_2 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 aa mean that xax_a is true (¬a\neg a means that xax_a is false). If someone's requirements are aa and bb, that is, (a∨b)(a \vee b) (at least one of the variables a,ba, b is satisfied), construct a directed graph for these relationships. Represent whether aa is true or false by vertices of the graph; ¬a→b\neg a\to b ¬b→a\neg b\to a means that if aa is false, then bb is necessarily true. Similarly, if bb is false, then aa is necessarily true. After constructing the graph, we can solve 2-SAT by contracting strongly connected components.

Original clauseImplication graph
¬a∨b\neg a \vee ba→ba \to b and ¬b→¬a\neg b \to \neg a
a∨ba \vee b¬a→b\neg a \to b and ¬b→a\neg b \to a
¬a∨¬b\neg a\vee\neg ba→¬ba \to \neg b and b→¬ab \to \neg a

Many 2-SAT problems require finding relationships such as: if aa is false, then bb 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 aa, determine whether the vertex representing aa being true and the vertex representing aa 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 xx comes after that of ¬x\neg x, set xx to true. After applying Tarjan's SCC contraction, this means that when the SCC containing xx has a number before that of ¬x\neg x, set xx 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 nn and mm are of the same order, computing the answer takes O(n)O(n) time, so the total complexity is O(n)O(n).

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 nn couples invited to a party. Because of venue constraints, only one person from each couple can attend. Among the 2n2n 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 nn people to attend simultaneously?

Following the analysis above, if the husband from a1a_1 conflicts with the wife from a2a_2, add an edge from the husband of a1a_1 to the husband of a2a_2, and from the wife of a2a_2 to the wife of a1a_1; 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 kk lamps, each red or blue, but their initial colors are unknown. There are nn 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;
}

Exercises

This content comes from OI Wiki: 2-SAT. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.