Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Connectivity

4.2.8 2-SAT

4-Graphs/4.2.8_2-SAT.cpp

View on GitHub

Solve a Boolean formula in 2-CNF, where each clause contains at most two literals. A clause like $(a \lor b)$ is represented by the implications $\lnot a \rightarrow b$ and $\lnot b \rightarrow a$. The formula is satisfiable if and only if no variable and its negation belong to the same strongly connected component of the implication graph.

Variables are numbered from $0$ to $n - 1$. A literal is represented by literal(variable, value), where value == true means the variable itself and value == false means its negation.

  • TwoSAT(n) constructs an empty formula over n variables.
  • literal(variable, value) returns the integer ID for a variable or its negation.
  • add_implication(a, b) adds the implication $a \rightarrow b$.
  • add_or(a, b) adds the clause $({\htmlClass{math-inline-code}{\texttt{a}}} \lor {\htmlClass{math-inline-code}{\texttt{b}}})$.
  • add_true(a) forces literal a to be true.
  • add_false(a) forces literal a to be false.
  • add(variable, value) forces a variable to equal value.
  • add(x, xval, y, yval) adds the clause $({\htmlClass{math-inline-code}{\texttt{x}}} = {\htmlClass{math-inline-code}{\texttt{xval}}} \lor {\htmlClass{math-inline-code}{\texttt{y}}} = {\htmlClass{math-inline-code}{\texttt{yval}}})$.
  • satisfiable() returns whether all added clauses can be satisfied and stores one valid assignment in answer.

Implementation

#include <algorithm>
#include <vector>

struct TwoSAT {
  int variables;
  std::vector<std::vector<int>> adj, rev;
  std::vector<int> order, component, answer;

  TwoSAT(int n = 0) : variables(n), adj(2 * n), rev(2 * n), answer(n, false) {}

  int literal(int variable, bool value) { return 2 * variable + (value ? 0 : 1); }
  int neg(int x) { return x ^ 1; }

  void add_implication(int a, int b) {
    adj[a].push_back(b);
    rev[b].push_back(a);
  }

  void add_or(int a, int b) {
    add_implication(neg(a), b);
    add_implication(neg(b), a);
  }

  void add_true(int a) { add_implication(neg(a), a); }
  void add_false(int a) { add_implication(a, neg(a)); }
  void add(int variable, bool value) { add_true(literal(variable, value)); }
  void add(int x, bool xval, int y, bool yval) { add_or(literal(x, xval), literal(y, yval)); }

  void dfs_order(int u, std::vector<bool> &visited) {
    visited[u] = true;
    for (int v : adj[u]) {
      if (!visited[v]) {
        dfs_order(v, visited);
      }
    }
    order.push_back(u);
  }

  void dfs_component(int u, int id) {
    component[u] = id;
    for (int v : rev[u]) {
      if (component[v] == -1) {
        dfs_component(v, id);
      }
    }
  }

  bool satisfiable() {
    order.clear();
    component.assign(2 * variables, -1);
    std::vector<bool> visited(2 * variables, false);
    for (int i = 0; i < 2 * variables; i++) {
      if (!visited[i]) {
        dfs_order(i, visited);
      }
    }
    std::reverse(order.begin(), order.end());
    int id = 0;
    for (int u : order) {
      if (component[u] == -1) {
        dfs_component(u, id++);
      }
    }
    for (int i = 0; i < variables; i++) {
      if (component[2 * i] == component[2 * i + 1]) {
        return false;
      }
      answer[i] = component[2 * i] > component[2 * i + 1];
    }
    return true;
  }
};

Example Usage

#include <cassert>
using namespace std;

int main() {
  TwoSAT solver(3);
  int x0 = solver.literal(0, true);
  int not_x0 = solver.literal(0, false);
  int x1 = solver.literal(1, true);
  int x2 = solver.literal(2, true);
  solver.add_or(x0, x1);
  solver.add_or(not_x0, x2);
  solver.add_true(x0);
  solver.add(1, true, 2, false);
  assert(solver.satisfiable());
  assert(solver.answer[0]);
  return 0;
}