Strongly Connected Components
Authors: Benjamin Qi, Dong Liu, Neo Wang, Rameez Parwez
Subsets of nodes in directed graphs where each node in a subset can reach each other node in the subset.
Strongly Connected Components (SCCs)
Focus Problem – try your best to solve this problem before continuing!
View Internal SolutionThe definition of a kingdom in this problem is equivalent to the definition of a strongly connected component. We can compute these components using either Kosaraju's or Tarjan's algorithms, both of which are described below.
Kosaraju's Algorithm
Resources | ||||
---|---|---|---|---|
CPH | ||||
Wikipedia | ||||
TC |
Explanation
In the first phase, the algorithm performs a depth-first search (DFS) on the original graph to determine the exit times of vertices; that is, the node that is processed last will be at the top of the stack (reverse topological sort). As each vertex finishes processing, it is pushed onto a stack. In the next phase, nodes are processed off the stack in the order of their decreasing finish times. This ordering will be used in the second phase.
In the second phase, the algorithm conducts another DFS, this time on the transposed graph, where all edges are reversed. The DFS processes nodes according to the order defined by the stack from the first phase. By reversing the edges and following this specific order, each DFS run in this phase can identify all vertices of one SCC before moving on to the next.
We know that the transposed graph will have the same strongly connected components (SCCs) as the original because reversing an edge doesn't change the reachability within the components. Since each SCC is both maximal and disjoint. This means that within an SCC, every pair of vertices is mutually reachable; you can get from any vertex to any other vertex , and vice versa. The term "maximal" indicates that you can't add any more vertices to an SCC without losing its strongly connected property. Furthermore, SCCs are disjoint, which means that no vertex can belong to more than one SCC. If a vertex were part of two SCCs, those SCCs would actually merge into a single, larger SCC. Therefore, reversing edges doesn't affect the undirected connections in the original graph.
During the second DFS, we process the vertices in the order determined by the stack from the first phase. This ensures that we start with the vertex that finished last in the initial DFS, which is guaranteed to be part of an SCC with no outgoing edges to other SCCs — essentially making it a "sink" in the transposed graph.
By processing the vertices in this specific order, we fully explore all vertices within the current SCC before moving on to the next one. SCCs are maximal subgraphs where every vertex can reach every other vertex within the same subgraph, so the DFS will traverse through all nodes in an SCC, isolating it completely. Once the entire SCC is processed, we move to the next highest exit time vertex that hasn’t been visited yet, ensuring that each SCC is handled independently and thoroughly, so no vertices from another SCC will be mistakenly included in the current one.
For visualization, refer to the TopCoder resource mentioned here.
Sink
A node is considered a sink in a graph if it has out-degree of 0 (except for the source node).
Implementation
Time Complexity:
C++
#include <bits/stdc++.h>const int N = 1e5 + 1;// adj_t is the transpose of adjstd::vector<int> adj[N], adj_t[N];std::vector<int> order;std::vector<int> vis(N), id(N);// calculates the order in which nodes are processed
Java
import java.io.*;import java.util.*;public class Main {static final int N = 100001;static boolean[] vis = new boolean[N + 1];// Adjacency list of neighborstatic List<Integer>[] adj = new ArrayList[N + 1];// adjT is the transpose of adjstatic List<Integer>[] adjT = new ArrayList[N + 1];
Python
Warning!
The below solution only runs in time if you submit with PyPy.
order = []def dfs1(v: int):"""Calculate the order to process nodes."""stack = [v]while stack:node = stack[-1]if not vis[node]:vis[node] = True
Tarjan's Algorithm
Resources | ||||
---|---|---|---|---|
CPC | ||||
CP2 | ||||
Wikipedia |
This section is not complete.
link to dpv in resources and just tell them to look at that, no way we're beating that explanation
Implementation
Time Complexity:
C++
#include <algorithm>#include <iostream>#include <vector>using std::cout;using std::endl;using std::vector;/** Takes in an adjacency list and calculates the SCCs of the graph. */class TarjanSolver {
Problems
Status | Source | Problem Name | Difficulty | Tags | |
---|---|---|---|---|---|
CSES | Easy | Show TagsDP, SCC | |||
POI | Easy | Show TagsDP, SCC | |||
CF | Normal | Show TagsDP, SCC | |||
Old Gold | Normal | Show TagsSCC | |||
CF | Normal | Show TagsSCC | |||
CF | Hard | Show TagsSCC | |||
POI | Hard | Show TagsSCC | |||
Kattis | Hard | Show TagsSCC | |||
CSES | Very Hard | Show TagsSCC |
2-SAT
Focus Problem – try your best to solve this problem before continuing!
Explanation
Introduction
The CSES problem already gives us a boolean formula in conjunctive normal form (CNF) that consists of a series of logical OR clauses ANDed together like so:
Before we proceed, try linking this to graph theory. Hint: represent a variable and its negation with two nodes.
Construction
Like the hint says, we can construct a graph with each variable having two nodes: one for itself and one for its negation. We're going to try to proceed by assigning each node a truth value. Note that the value of one of the variable's nodes determines the other, since if we know the value of one node, the other is the negation of that value.
Now, for each clause , we add two directed edges: and . What this means for us is that if was false, then must be true, and vice versa.
With these edges, an SCC implies a group of values that all have to have the same truth value.
Solving the Graph
The only way for there to be an impossible assignment of truth values is if a node and its negation are in the same SCC, since this means that a boolean and its negation have to both be true, which is impossible.
If the graph is consistent and there are no impossible configurations, we can start assigning truth values, starting from the SCCs that have no outgoing edges to other SCCs and proceeding backwards. With the initial SCCs, we set them all to true. As for other SCCs, if a value has already been assigned due to its negation being in a previously processed component, we have to assign all other values in the component to that value.
Due to certain properties about the graph we've constructed, we can guarantee that the resulting assignment of the variables does not have any "true" SCCs leading to "false" SCCs. A proof of this is beyond the scope of this module.
Implementation
We use Tarjan's algorithm as it already provides us with a topological order to process the nodes in. However, it is also possible to use Kosaraju's algorithm.
Time Complexity:
C++
#include <algorithm>#include <iostream>#include <vector>using std::cout;using std::endl;using std::vector;Code Snippet: SCC Solver (Click to expand)
Problems
Status | Source | Problem Name | Difficulty | Tags | |
---|---|---|---|---|---|
CF | Easy | Show Tags2SAT | |||
CF | Easy | Show Tags2SAT, DFS, DSU | |||
CC | Easy | Show Tags2SAT, DSU, Greedy, Sliding Window | |||
Kattis | Easy | Show Tags2SAT | |||
AC | Normal | Show Tags2SAT | |||
CF | Hard | Show Tags2SAT, Binary Search, Trees | |||
CF | Hard | Show Tags2SAT, DFS |
Module Progress:
Join the USACO Forum!
Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!