A tree is a connected acyclic graph. These are equivalent for a graph on vertices: connected and acyclic; connected with edges; acyclic with edges; exactly one simple path between any two vertices; minimally connected (every edge is a bridge); maximally acyclic (any added edge closes exactly one cycle).

Basic facts

Fact
Edges
Degree sum
Leavesat least 2 (for )
Adding any edgecreates exactly one cycle
Removing any edgecreates exactly two components
Every tree is bipartite2-colour by depth parity
Labelled trees on vertices (Cayley’s formula, proved via Prüfer codes in Isomorphism and Prüfer Codes)
Forest with components edges

Types of trees

Every later note works with some specialisation of “tree”. This is the map: what each kind actually is, where it is actually used in CP (not just CS theory), and which note implements it. “Free tree” above means no designated root; everything below assumes a root unless noted.

TypeWhat it isUsed for in CPNote
Free / rooted tree, forestconnected acyclic graph; a forest is a disjoint union of treesthe base object of this whole chapter—
Binary treeeach node has children, distinguished left/rightthe shape behind recursion, heaps, Huffman, expression trees—
Full / complete / perfect binary treefull: every node has 0 or 2 children; complete: every level full except possibly the last, filled left to right; perfect: every leaf at the same depth, nodesbinary heaps are complete binary trees stored in an array (no pointers, parent/child index)Heaps
Binary search tree (BST)binary tree, in-order = sorted orderthe model; almost never hand-rolled unbalanced in CP (worst case depth)—
Balanced BST (AVL, red-black, weight-balanced)BST with a rebalancing invariant bounding height to textbook-important, essentially never implemented from scratch in a contest — std::set/map (red-black internally) or a treap give the same guarantees with far less codeSTL Containers
TreapBST keyed by value, heap-ordered by an independent random prioritythe CP balanced BST: split/merge, implicit keys (order statistics without stored keys), persistenceTreaps and Balanced BSTs
Splay treeBST that rotates the accessed node to the rootamortized with no stored balance info; the link-cut tree’s per-node structureTreaps and Balanced BSTs, Link-Cut Tree
B-tree / B+-treewide-fanout balanced search tree, depthdatabase/filesystem indexing; rare in CP, relevant mainly for external-memory argumentsTreaps and Balanced BSTs
Trietree keyed by the characters/bits of a path from the rootstring sets with a common-prefix structure, Aho–Corasick, bitwise max-XOR triesTrie and Aho–Corasick
Cartesian treeheap-ordered by value, BST-ordered by index, built in by a monotonic stackthe RMQ LCA reduction; “divide at the minimum” recursionsMonotonic Stack, Range Minimum Query
Segment tree / Fenwick treean implicit binary tree over array positions (segment tree) or over bit-prefixes (Fenwick), never built as pointersthe default range-query structure; the tree is there for correctness reasoning even though the code has no node objectsSegment Tree, Fenwick Tree
k-d treebinary tree splitting points by alternating coordinatenearest-neighbour / orthogonal range queries in low dimensionsKD-Tree and Spatial Structures
Suffix treecompressed trie of all suffixes of a stringpattern matching, substring structure; usually superseded by the suffix automaton in CPSuffix Tree
Spanning tree / MSTa tree subgraph touching every vertex of a (connected) graph; MST minimises total weightconnectivity backbones, Kruskal reconstruction trees, network designMinimum Spanning Trees
Kruskal reconstruction treebinary tree built by Kruskal’s merge order, internal nodes hold the merging edge’s weightoffline “max edge weight on path ” / “smallest connecting ” queries via LCAKruskal Reconstruction Tree
Centroid treerecursive centroid-decomposition structure, height path-counting problems with an divide stepCentroid Decomposition
Heavy-light / long-path skeletonthe original tree re-viewed as chainspath queries and updates via segment trees over chainsHeavy-Light Decomposition
Virtual (auxiliary) treethe Steiner tree of a marked vertex set plus their pairwise LCAsDP restricted to marked vertices in total instead of Virtual Tree
Link-cut treesplay-tree-based structure supporting link, cut, path queries on a dynamic forestdynamic connectivity / path problems where the tree itself changes onlineLink-Cut Tree
Euler tour treebalanced sequence (treap) storing a forest’s Euler tourdynamic forest connectivity and subtree aggregates under link/cutEuler Tour Tree
Top treehierarchy of clusters built from edges by two merge rulesthe most general dynamic-tree structure; path and subtree operations, non-local recombinationTop Trees
Persistent tree (segment tree, treap, trie)any of the above rebuilt so every update creates new nodes, sharing the rest”query an old version”, rollback, functional/persistent data structuresPersistence

Rooting without recursion

struct RootedTree {                                     // parent, depth, BFS order, subtree sizes
    int n; vector<vector<int>> adj; vector<int> par, dep, order, sub;
    RootedTree(int n) : n(n), adj(n), par(n, -1), dep(n), sub(n, 1) {}
    void addEdge(int u, int v) { adj[u].push_back(v), adj[v].push_back(u); }
    void root(int r) {
        order = {r}; par[r] = -1; dep[r] = 0;
        for (int i = 0; i < sz(order); i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], dep[v] = dep[order[i]] + 1, order.push_back(v);
        for (int i = n - 1; i > 0; i--) sub[par[order[i]]] += sub[order[i]];
    }
};

Processing order backwards is a post-order: every child before its parent. Use it for all “on the way up” DP when recursion depth ( path) is a risk.

Recursion depth

A path-shaped tree with overflows the default stack on some judges if rooted recursively. RootedTree::root above is the iterative replacement; use it (or an explicit stack) whenever depth isn’t bounded well below the recursion limit.

OrderWhen each node is emittedUse
Preorderon entrycopying, prefix expressions, Euler tour
Postorderon exitsubtree aggregation, deletion, tree DP
Inorderbetween children (binary trees)sorted order in a BST
Level order (BFS)by depthshortest paths, level statistics

Edge contribution. An edge whose lower side has vertices lies on the paths of exactly pairs, so . The same “count per edge, not per path” move handles sums of path lengths over subsets, expected distances, and colour-pair counts.

Diameter

The diameter is the longest simple path between any two vertices — in edges, or in total weight for a weighted tree.

pair<int, ll> farthest(const vector<vector<pair<int, ll>>>& adj, int src, vector<ll>& d) {   // weights >= 0
    int n = sz(adj); d.assign(n, -1); d[src] = 0; vector<int> st{src};
    while (sz(st)) { int u = st.back(); st.pop_back(); for (auto [v, w] : adj[u]) if (d[v] < 0) d[v] = d[u] + w, st.push_back(v); }
    int b = int(max_element(all(d)) - d.begin()); return {b, d[b]};
}
// diameter endpoints a, b: a = farthest(any), b = farthest(a); ecc(v) = max(dist(a, v), dist(b, v))
ll diameterDP(const vector<vector<pair<int, ll>>>& adj, int r = 0) {   // any weights; best path with >= 1 vertex
    int n = sz(adj); vector<int> par(n, -1), order{r}; vector<ll> down(n, 0); ll best = 0;
    for (int i = 0; i < sz(order); i++) for (auto [v, w] : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], order.push_back(v);
    for (int i = n - 1; i >= 0; i--) {
        int u = order[i]; ll d1 = 0, d2 = 0;                       // two best downward extensions (>= 0)
        for (auto [v, w] : adj[u]) if (v != par[u]) { ll d = max(0LL, down[v] + w); if (d > d1) d2 = d1, d1 = d; else if (d > d2) d2 = d; }
        down[u] = d1; best = max(best, d1 + d2);
    }
    return best;
}
  • Two sweeps need non-negative weights; the DP works for any weights (it returns the heaviest path, possibly a single vertex).
  • Eccentricity of every vertex is for diameter endpoints : two traversals give all of them.
  • Joining two trees by one edge to minimise the new diameter: connect their centres; the result is (unit weights).
  • All diameters share the centre: every diameter passes through the centre(s).
  • Two centres iff the diameter has odd length (an even number of vertices on the diameter path); otherwise there is exactly one, the diameter’s midpoint.

Centre and centroid

CentreCentroid
minimiseseccentricity (max distance)largest remaining component / sum of distances
count1 or 2 (adjacent)1 or 2 (adjacent)
findpeel leaves, or middle of a diameterone DFS on subtree sizes
used forcanonical root, minimum height (= radius )centroid decomposition, isomorphism root, the tree median

The centre is the canonical choice when an unrooted tree needs a root for comparison or hashing: it is defined purely by the tree’s structure, so isomorphic trees have corresponding centres, and there are at most two to try — AHU isomorphism testing roots one tree at its centre and tries both centres of the other. Facility location: placing one facility on a tree to minimise the worst-case distance means placing it at the centre (or at the midpoint of the diameter edge itself, if fractional positions are allowed and the diameter has even length).

vector<int> treeCenters(const vector<vector<int>>& adj) {      // peel leaves layer by layer
    int n = sz(adj); if (n == 1) return {0};
    vector<int> deg(n), layer; for (int i = 0; i < n; i++) if ((deg[i] = sz(adj[i])) <= 1) layer.push_back(i);
    for (int removed = sz(layer); removed < n;) {
        vector<int> nxt; for (int u : layer) for (int v : adj[u]) if (--deg[v] == 1) nxt.push_back(v);
        removed += sz(nxt); layer = nxt;
    }
    return layer;
}
vector<int> treeCentroids(const vector<vector<int>>& adj) {
    int n = sz(adj); vector<int> par(n, -1), order{0}, sub(n, 1), res;
    for (int i = 0; i < n; i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], order.push_back(v);
    for (int i = n - 1; i > 0; i--) sub[par[order[i]]] += sub[order[i]];
    for (int u = 0; u < n; u++) {
        int mx = n - sub[u]; for (int v : adj[u]) if (v != par[u]) mx = max(mx, sub[v]);
        if (2 * mx <= n) res.push_back(u);
    }
    return res;
}

Dynamic diameter under weight changes

On the Euler tour with root distances (vertex appears after each child returns), the diameter is : the middle index plays the LCA. A lazy segment tree keeps , , , and the full triple; changing an edge weight adds a constant on the subtree’s tour interval. per update. (CEOI 2019 “Diameter” is the direct showcase for this structure.)

struct DynamicDiameter {                                       // weights >= 0, rooted at 0
    struct Node { ll a, b, ab, bc, abc, lz = 0; };            // max d, max -2d, max d_i-2d_j, max -2d_j+d_k, max triple
    int n, m; vector<int> tin, tout, lower; vector<ll> w; vector<Node> t;
    static Node merge(const Node& x, const Node& y) {
        return {max(x.a, y.a), max(x.b, y.b), max({x.ab, y.ab, x.a + y.b}), max({x.bc, y.bc, x.b + y.a}),
                max({x.abc, y.abc, x.ab + y.a, x.a + y.bc}), 0};
    }
    static void apply(Node& x, ll v) { x.a += v, x.b -= 2 * v, x.ab -= v, x.bc -= v, x.lz += v; }
    DynamicDiameter(int n, const vector<tuple<int, int, ll>>& edges) : n(n), tin(n), tout(n), lower(sz(edges)), w(sz(edges)) {
        vector<vector<pair<int, int>>> adj(n);
        for (int e = 0; e < sz(edges); e++) { auto [u, v, x] = edges[e]; adj[u].push_back({v, e}), adj[v].push_back({u, e}); w[e] = x; }
        vector<ll> tour{0}, d(n); vector<int> it(n), pe(n, -1), st{0};
        while (sz(st)) {
            int u = st.back();
            if (it[u] < sz(adj[u])) {
                auto [v, e] = adj[u][it[u]++];
                if (e == pe[u]) continue;
                pe[v] = e, lower[e] = v, d[v] = d[u] + w[e], tin[v] = sz(tour); tour.push_back(d[v]); st.push_back(v);
            } else { st.pop_back(); tout[u] = sz(tour) - 1; if (sz(st)) tour.push_back(d[st.back()]); }
        }
        m = sz(tour); t.assign(4 * m, Node()); build(1, 0, m, tour);
    }
    void build(int p, int l, int r, const vector<ll>& tour) {
        if (r - l == 1) { ll x = tour[l]; t[p] = {x, -2 * x, -x, -x, 0, 0}; return; }
        int mid = (l + r) / 2; build(2 * p, l, mid, tour); build(2 * p + 1, mid, r, tour); t[p] = merge(t[2 * p], t[2 * p + 1]);
    }
    void add(int p, int l, int r, int x, int y, ll v) {
        if (y <= l || r <= x) return;
        if (x <= l && r <= y) return apply(t[p], v);
        if (t[p].lz) apply(t[2 * p], t[p].lz), apply(t[2 * p + 1], t[p].lz), t[p].lz = 0;
        int mid = (l + r) / 2; add(2 * p, l, mid, x, y, v); add(2 * p + 1, mid, r, x, y, v); t[p] = merge(t[2 * p], t[2 * p + 1]);
    }
    void setWeight(int e, ll nw) { int v = lower[e]; add(1, 0, m, tin[v], tout[v] + 1, nw - w[e]); w[e] = nw; }
    ll diameter() const { return t[1].abc; }
};

Other tools

  • Binarisation: replace a vertex with children by a chain of 0-weight dummies, each keeping one child; distances between original vertices are unchanged and every degree is (needed for edge-centroid decomposition and some top-tree constructions).
  • Morris traversal: in-order traversal of a binary tree in extra space by temporarily threading each in-order predecessor’s empty right pointer back to the node. Variants: visit the node before descending left for pre-order instead of after (in-order); the same thread-rewiring trick underlies -space BST-property validation and flattening a binary tree to a linked list.
template <class T> vector<vector<pair<int, T>>> binarizeTree(const vector<vector<pair<int, T>>>& adj, int root) {
    int n = sz(adj); vector<vector<pair<int, T>>> b(n);
    auto addEdge = [&](int u, int v, T w) { b[u].push_back({v, w}); b[v].push_back({u, w}); };
    vector<int> par(n, -1), order{root}; par[root] = root;
    for (int i = 0; i < sz(order); i++) {
        int u = order[i]; vector<pair<int, T>> ch;
        for (auto [v, w] : adj[u]) if (par[v] == -1) par[v] = u, order.push_back(v), ch.push_back({v, w});
        int last = u;                                           // u keeps ch[0]; a 0-weight dummy chain takes the rest
        for (int j = 0; j < sz(ch); j++) {
            if (j > 0 && j + 1 < sz(ch)) { int d = sz(b); b.emplace_back(); addEdge(last, d, T{}); last = d; }
            addEdge(last, ch[j].first, ch[j].second);
        }
    }
    return b;
}
struct BNode { int val; BNode *left = nullptr, *right = nullptr; };
vector<int> morrisInorder(BNode* cur) {
    vector<int> res;
    while (cur) {
        if (!cur->left) { res.push_back(cur->val); cur = cur->right; continue; }
        BNode* pre = cur->left;
        while (pre->right && pre->right != cur) pre = pre->right;
        if (!pre->right) pre->right = cur, cur = cur->left;               // thread, go left
        else pre->right = nullptr, res.push_back(cur->val), cur = cur->right;   // second visit: unthread
    }
    return res;
}