Strongly connected components partition a digraph into maximal mutually reachable sets; contracting them gives a DAG (the condensation), on which everything that cycles made hard becomes DP. 2-SAT is satisfiability of 2-literal clauses, decided by SCCs of the implication graph in linear time.

Tarjan (iterative, components in topological order)

struct SCC {
    int cnt = 0; vector<int> comp;                          // comp[v] in [0, cnt), topological order of the condensation
    SCC(const vector<vector<int>>& g) {
        int n = sz(g), timer = 0; comp.assign(n, -1);
        vector<int> tin(n, -1), low(n), it(n), stk, call;
        for (int r = 0; r < n; r++) {
            if (tin[r] >= 0) continue;
            tin[r] = low[r] = timer++; stk.push_back(r); call.push_back(r);
            while (sz(call)) {
                int u = call.back();
                if (it[u] < sz(g[u])) {
                    int v = g[u][it[u]++];
                    if (tin[v] < 0) { tin[v] = low[v] = timer++; stk.push_back(v); call.push_back(v); }
                    else if (comp[v] < 0) low[u] = min(low[u], tin[v]);   // v still on the stack
                    continue;
                }
                call.pop_back();
                if (sz(call)) low[call.back()] = min(low[call.back()], low[u]);
                if (low[u] == tin[u]) { int v; do { v = stk.back(); stk.pop_back(); comp[v] = cnt; } while (v != u); cnt++; }
            }
        }
        for (int& c : comp) c = cnt - 1 - c;                 // Tarjan emits sinks first
    }
    vector<vector<int>> dag(const vector<vector<int>>& g) const {   // condensation edges, deduplicated
        vector<vector<int>> d(cnt);
        for (int u = 0; u < sz(g); u++) for (int v : g[u]) if (comp[u] != comp[v]) d[comp[u]].push_back(comp[v]);
        for (auto& x : d) { sort(all(x)); x.erase(unique(all(x)), x.end()); }
        return d;
    }
};

Kosaraju is the two-pass alternative: DFS to get finishing order, then DFS on the reversed graph in decreasing finishing order; each tree is an SCC, found in topological order. Gabow’s is a one-pass alternative to Tarjan that replaces the low array with a second stack, slightly shorter code and marginally faster in practice.

PassesExtra arraysNeeds Component order
Kosaraju2order, usedyestopological
Tarjan1tin, low, onStacknoreverse topological
Gabow1tin, two stacksnoreverse topological

All three are ; check which direction your implementation emits before relying on it downstream.

The condensation

QuestionAnswer on the condensation
maximum weight collectible on a walkDAG longest path with component weights (CSES Coin Collector)
all-pairs reachabilitybitset DP over the DAG (DAGs)
minimum starting points so everything is reachednumber of sources
minimum edges to add for strong connectivity if more than one SCC, else 0
vertices reachable from everyonethe unique sink component if there is exactly one, else none
2-SATcompare component ids

2-SAT

Variables ; literal is vertex , is . Clause gives edges and . Satisfiable iff no shares an SCC with ; then set true iff comes after in topological order.

struct TwoSat {
    int n; vector<vector<int>> g;
    TwoSat(int n) : n(n), g(2 * n) {}
    int lit(int i, bool val) { return 2 * i + !val; }
    void either(int a, bool va, int b, bool vb) {           // (x_a == va) OR (x_b == vb)
        g[lit(a, !va)].push_back(lit(b, vb)); g[lit(b, !vb)].push_back(lit(a, va));
    }
    void setValue(int a, bool v) { either(a, v, a, v); }
    void implies(int a, bool va, int b, bool vb) { either(a, !va, b, vb); }
    optional<vector<bool>> solve() {
        SCC s(g); vector<bool> res(n);
        for (int i = 0; i < n; i++) {
            if (s.comp[2 * i] == s.comp[2 * i + 1]) return nullopt;
            res[i] = s.comp[2 * i] > s.comp[2 * i + 1];
        }
        return res;
    }
};
ConstraintClauses
/ /
at most one of prefix variables : , , ( clauses instead of )
exactly one of at most one + one clause is not 2-SAT unless

Patterns: two choices per object with pairwise conflicts (labels above/below points, segments left/right), “binary search the answer, then 2-SAT feasibility”, 2-colouring with extra implications. For many conflicts from geometry or ranges, add edges to segment-tree nodes of literals instead of pairs.

Lexicographically smallest satisfying assignment: try false in order and propagate implications (DFS), undoing on contradiction: . Counting satisfying assignments of 2-SAT is p-complete.

Incremental SCC

Edges are added in order; for each edge compute the first prefix after which its endpoints are strongly connected. Divide and conquer on time: for a time range with its candidate edges, run one SCC on edges with index (over DSU-contracted vertices); edges that close by mid go left, the rest right; solve left, contract its merged edges, solve right. SCC work. Sorting edges by their times and uniting them in a DSU then gives the SCCs of every prefix (Library Checker “Incremental SCC”).

vector<int> incrementalSCC(int n, const vector<pair<int, int>>& E) {   // t[i] in [i, m]; m = never
    int m = sz(E); vector<int> t(m, m), dsu(n); iota(all(dsu), 0);
    auto find = [&](int x) { while (dsu[x] != x) x = dsu[x] = dsu[dsu[x]]; return x; };
    vector<int> id(n, -1);
    auto rec = [&](auto&& self, int l, int r, vector<int>& es) -> void {
        if (es.empty()) return;
        if (l == r) { for (int e : es) t[e] = l; return; }
        int mid = (l + r) / 2; vector<int> vs;
        for (int e : es) for (int x : {find(E[e].first), find(E[e].second)}) if (id[x] < 0) id[x] = sz(vs), vs.push_back(x);
        vector<vector<int>> g(sz(vs));
        for (int e : es) if (e <= mid) g[id[find(E[e].first)]].push_back(id[find(E[e].second)]);
        SCC s(g); vector<int> left, right;
        for (int e : es) (e <= mid && s.comp[id[find(E[e].first)]] == s.comp[id[find(E[e].second)]] ? left : right).push_back(e);
        for (int x : vs) id[x] = -1;
        self(self, l, mid, left);
        for (int e : left) { int a = find(E[e].first), b = find(E[e].second); if (a != b) dsu[a] = b; }
        self(self, mid + 1, r, right);
    };
    vector<int> es(m); iota(es.begin(), es.end(), 0);
    rec(rec, 0, m, es);
    return t;
}

Condensing loses information

A self-loop disappears into its component when contracting — if “can we stay here forever” matters, record self-loops separately before condensing. Contracting can also produce many parallel edges between two components; the dag() helper above deduplicates them, but a hand-rolled version that doesn’t will silently double-count anything sensitive to edge multiplicity.