Given a set of important vertices in a tree of vertices, build a compressed tree on nodes that preserves all the ancestor relationships among them. Then run your algorithm on the small tree instead of the big one.

Why it exists

A common problem shape: “you are given queries, each with a set of vertices , and .” A per-query tree DP is — far too slow. The virtual tree makes each query , so the total is near-linear.

The construction

  1. Sort the important vertices by DFS entry time tin.
  2. Add the LCA of each adjacent pair in that order. These are the only extra vertices needed — a fact worth remembering, and the reason the tree stays .
  3. Sort the combined set by tin again and deduplicate.
  4. Build the tree with a stack: for each vertex in order, pop until the stack top is an ancestor, then attach.
vector<int> buildVirtualTree(vector<int> vs) {
    sort(vs.begin(), vs.end(), [](int a, int b){ return tin[a] < tin[b]; });
    int k = vs.size();
    for (int i = 0; i + 1 < k; i++) vs.push_back(lca(vs[i], vs[i+1]));
    sort(vs.begin(), vs.end(), [](int a, int b){ return tin[a] < tin[b]; });
    vs.erase(unique(vs.begin(), vs.end()), vs.end());
 
    for (int v : vs) vadj[v].clear();
    vector<int> st;
    for (int v : vs) {
        while (!st.empty() && !isAncestor(st.back(), v)) st.pop_back();
        if (!st.empty()) { vadj[st.back()].push_back(v); vpar[v] = st.back(); }
        st.push_back(v);
    }
    return vs;                                    // vs[0] is the root
}
 
bool isAncestor(int u, int v) { return tin[u] <= tin[v] && tout[v] <= tout[u]; }

Clear only the vertices you used (for (int v : vs) vadj[v].clear()), never the whole array — otherwise each query costs and you have gained nothing.

Why adjacent-pair LCAs suffice

The virtual tree must contain every vertex where two important subtrees branch apart. In DFS order, any such branching vertex is the LCA of two consecutive important vertices — if and branch at , then some consecutive pair in between also has as its LCA. So LCAs are enough, giving at most vertices total.

Carrying edge information

A virtual edge represents a whole path in the original tree. Store what the DP needs:

int weight = dep[v] - dep[u];                     // number of original edges
long long cost = distToRoot[v] - distToRoot[u];   // path weight

Also record whether the endpoints are important or merely LCA helpers — the DP usually treats them differently.

Typical problems

ProblemVirtual tree DP
Minimum cost to disconnect the important vertices from the roottree DP with min(edge cost, sum of children)
Sum of pairwise distances among the important verticesedge-contribution DP
Number of paths between important vertices passing through each vertexsubtree counts
Closest important vertex to each important vertextwo-pass DP
Whether the important vertices form a connected subtreecheck the virtual tree’s structure

Complexity

  • Preprocessing (once): for LCA.
  • Per query: for sorting plus for the LCAs.
  • Total over all queries: .
TechniqueCompressesPreserves
Virtual treea vertex subsetancestor relationships
CondensationSCCsreachability
Bridge tree2-edge-connected componentsedge connectivity
Block-cut treebiconnected componentsvertex connectivity
Centroid decompositionthe whole treepath structure, depth

All five share the same instinct: replace a big structure with a small one that keeps the property you care about.

See also: LCA · Tree DP · Euler Tour