Detecting and extracting cycles in undirected and directed graphs, and the structure of functional graphs (every vertex has exactly one outgoing edge nxt[v]): each weak component is one directed cycle with in-trees hanging off it. Permutations, “next pointer” processes, iterated functions and pseudo-random sequences are functional graphs. Shortest cycles (girth), minimum mean cycles and negative cycles are in Shortest Path Techniques and Shortest Paths.

Finding a cycle

  • Undirected: DFS; a visited neighbour reached by an edge other than the one we came in on closes a cycle. Skip the parent edge id, not the parent vertex, or multi-edges are missed — a self-loop is a cycle by itself and needs no parent check at all. Alternatively, an edge whose endpoints are already connected in a DSU closes a cycle (this is also how Kruskal avoids cycles). A graph is a forest iff .
  • Directed: three colours; an edge to a grey vertex (on the current DFS path) is a back edge and closes a cycle. Edges to black vertices are harmless. Equivalently, Kahn’s algorithm leaves vertices unprocessed, or some SCC has size — or size 1 with a self-loop, which Kahn’s/SCC-counting must also catch.
vector<int> findDirectedCycle(const vector<vector<int>>& g) {     // empty if acyclic
    int n = sz(g); vector<int> col(n), par(n, -1), cyc;
    auto dfs = [&](auto&& self, int u) -> bool {
        col[u] = 1;
        for (int v : g[u]) {
            if (col[v] == 1) { for (int x = u; x != v; x = par[x]) cyc.push_back(x); cyc.push_back(v); reverse(all(cyc)); return true; }
            if (!col[v]) { par[v] = u; if (self(self, v)) return true; }
        }
        col[u] = 2; return false;
    };
    for (int s = 0; s < n; s++) if (!col[s] && dfs(dfs, s)) return cyc;
    return {};
}

The cycle space of an undirected graph (edge sets with all degrees even, under XOR) has dimension ; the fundamental cycles of any spanning forest form a basis. That is why “XOR of a path” problems reduce to a tree path plus a linear basis of cycle XORs (Shortest Path Techniques, XOR Basis).

Functional graphs

struct FunctionalGraph {
    int n, LOG = 1; vector<int> nxt, cycleOf, pos, tail, entry; vector<vector<int>> cycles, up;
    FunctionalGraph(const vector<int>& f) : n(sz(f)), nxt(f), cycleOf(n, -1), pos(n, -1), tail(n, -1), entry(n, -1) {
        while ((1 << LOG) <= n) LOG++;
        vector<int> st(n);
        for (int s = 0; s < n; s++) {
            if (st[s]) continue;
            vector<int> path; int v = s;
            while (!st[v]) st[v] = 1, path.push_back(v), v = nxt[v];
            if (st[v] == 1) {                                   // new cycle through v
                vector<int> cyc(find(all(path), v), path.end());
                for (int i = 0; i < sz(cyc); i++) cycleOf[cyc[i]] = sz(cycles), pos[cyc[i]] = i, tail[cyc[i]] = 0, entry[cyc[i]] = cyc[i];
                cycles.push_back(cyc);
            }
            for (int i = sz(path) - 1; i >= 0; i--) {
                int u = path[i]; st[u] = 2;
                if (tail[u] < 0) tail[u] = tail[nxt[u]] + 1, entry[u] = entry[nxt[u]], cycleOf[u] = cycleOf[nxt[u]];
            }
        }
        up.assign(LOG, nxt);
        for (int j = 1; j < LOG; j++) for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];
    }
    int lift(int v, ll k) const { for (int j = 0; k; j++, k >>= 1) if (k & 1) v = up[j][v]; return v; }   // k < 2^LOG
    int jump(int v, ll k) const {                               // nxt applied k times, any k
        if (k <= tail[v]) return lift(v, k);
        int w = entry[v]; const auto& c = cycles[cycleOf[w]];
        return c[(pos[w] + (k - tail[v]) % sz(c)) % sz(c)];
    }
    ll dist(int u, int v) const {                               // min k with jump(u, k) == v, or -1
        if (cycleOf[u] != cycleOf[v]) return -1;
        if (!tail[v]) { int L = sz(cycles[cycleOf[v]]); return tail[u] + ((pos[v] - pos[entry[u]]) % L + L) % L; }
        if (tail[u] < tail[v]) return -1;
        return lift(u, tail[u] - tail[v]) == v ? tail[u] - tail[v] : -1;
    }
};

Typical uses: -th successor for (CSES Planets Queries I), distance between two vertices (Planets Queries II), cycle length reached from each vertex (Planets Cycles), “after applications of a permutation”, simulation of deterministic processes with a small state space (find the period, then jump).

TaskMethod
find the cycle from one start vertexFloyd or Brent, time, space
find all cycles over the whole graphone colour-DFS / timestamp pass (the FunctionalGraph constructor above)
-th successor, any single binary lifting, , or jump above in after preprocessing
cycle length only, no start point neededthe first phase of Brent’s algorithm alone suffices

-memory cycle finding (implicit sequences)

For with a huge state space (Pollard’s rho, PRNG periods): tail length and cycle length .

template<class T, class F> pair<ll, ll> brent(T x0, F f) {    // {mu, lambda}
    ll power = 1, lam = 1; T tort = x0, hare = f(x0);
    while (!(tort == hare)) { if (power == lam) tort = hare, power *= 2, lam = 0; hare = f(hare), lam++; }
    tort = hare = x0; for (ll i = 0; i < lam; i++) hare = f(hare);
    ll mu = 0; while (!(tort == hare)) tort = f(tort), hare = f(hare), mu++;
    return {mu, lam};
}

Floyd’s version: slow moves 1, fast moves 2 until they meet; then restart slow from and move both by 1; they meet at index .

Classic applications of Floyd/Brent beyond an explicit nxt array: duplicate detection in an array of values in by treating as the functional graph (the duplicate is exactly the cycle’s entry point); the Happy Number problem (iterate digit-square-sum, a cycle means “not happy”); finding a linked list’s middle node as a byproduct of the slow/fast scan before reversing for a palindrome check.

Permutations as functional graphs

A permutation is a functional graph with in-degree 1: a disjoint union of cycles (no tails).

  • The order of is the lcm of its cycle lengths; splits each cycle of length into cycles of length .
  • Parity of = parity of ; minimum number of swaps to sort = ; minimum adjacent swaps = inversions.
  • -th roots: a cycle of length in becomes cycles of length in . So has a -th root iff, for every length , the number of -cycles of is a sum of admissible group sizes , those with (interleave cycles of length into one of length ). For : odd cycles are unrestricted, cycles of each even length must come in pairs.