DSU (union-find) maintains a partition of elements under two operations: find(v) — which set is in — and unite(a,b) — merge two sets. With both optimisations, effectively per operation, where is the inverse Ackermann function and for any you will ever see.

Implementation

struct DSU {
    vector<int> par, sz;
    int comps;
 
    DSU(int n) : par(n), sz(n, 1), comps(n) { iota(par.begin(), par.end(), 0); }
 
    int find(int v) { return par[v] == v ? v : par[v] = find(par[v]); }   // path compression
 
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (sz[a] < sz[b]) swap(a, b);                                   // union by size
        par[b] = a;
        sz[a] += sz[b];
        comps--;
        return true;
    }
    bool same(int a, int b) { return find(a) == find(b); }
    int size(int v) { return sz[find(v)]; }
};

unite returning false when the elements were already together is what makes Kruskal a five-line loop.

The two optimisations

OptimisationAloneTogether
Neither per op
Union by size/rank only
Path compression only amortized
Both amortized

Union by size keeps the tree shallow; path compression flattens it on the way back up. Tarjan proved the combined bound is amortized, and that this is optimal for any pointer-based structure.

Path compression breaks rollback

If you need to undo unions (for offline dynamic connectivity), you must drop path compression and use union by size only — find becomes , which is the price of undo. See DSU Variants.

What DSU is for

ProblemHow
Connected componentsunite every edge
Kruskal MSTadd an edge iff unite succeeds
Cycle detection (undirected)an edge whose endpoints are already united closes a cycle
Number of componentsmaintain a counter
Component sizessz[find(v)]
Offline LCAunion subtrees as the DFS finishes them
Percolation / grid mergingtreat cells as elements
”Merge intervals” / “next free slot”see below
Equations with contradictionsunite, then check
Image connected componentsflood fill alternative

The “next free slot” trick

A remarkably useful idiom: use DSU as a pointer to the next unused position.

// par[i] = the smallest free index >= i
int nextFree(int i) { return par[i] == i ? i : par[i] = nextFree(par[i]); }
void occupy(int i) { par[i] = i + 1; }        // point past this slot

This solves “assign each request the earliest available slot” in near-linear time, and turns “paint the range , but only cells not yet painted” into an amortized sweep — each cell is painted once and then skipped forever.

// paint [l, r] with colour c, skipping already-painted cells
for (int i = nextFree(l); i <= r; i = nextFree(i + 1)) {
    colour[i] = c;
    occupy(i);
}

DSU on a grid

Map to . Add a virtual node for “outside the grid” when the problem asks about connections to the border — a common trick in percolation and island problems.

What DSU cannot do

  • Split a set (there is no separate operation). If you need deletions, process the queries in reverse, or use offline dynamic connectivity.
  • Answer queries about paths between elements — that needs link-cut trees.
  • Handle directed reachability — see SCC.

See also: DSU Variants · Dynamic Connectivity · Kruskal