For a set of marked vertices, the virtual (auxiliary) tree keeps plus the LCAs needed to preserve their ancestor structure: at most vertices, each edge standing for a compressed path. Queries with then run a tree DP in each instead of .

Construction: sort by tin, add the LCA of every adjacent pair, sort and deduplicate again; then the virtual parent of each vertex (except the first) is the LCA of it and its predecessor in this order.

pair<vector<int>, vector<int>> virtualTree(const LCA& L, vector<int> vs) {   // {vertices by tin, virtual parent (-1 for root)}
    auto byTin = [&](int a, int b) { return L.tin[a] < L.tin[b]; };
    sort(all(vs), byTin); vs.erase(unique(all(vs)), vs.end());
    for (int i = 0, k = sz(vs); i + 1 < k; i++) vs.push_back(L.lca(vs[i], vs[i + 1]));
    sort(all(vs), byTin); vs.erase(unique(all(vs)), vs.end());
    vector<int> vpar(sz(vs), -1);
    for (int i = 1; i < sz(vs); i++) vpar[i] = L.lca(vs[i - 1], vs[i]);
    return {vs, vpar};                                     // edge (vs[i], vpar[i]) has length dep[vs[i]] - dep[vpar[i]]
}

Process vertices in reverse order of the returned list for bottom-up DP (children come after parents). Keep per-query arrays indexed by vertex id but reset only the touched vertices, never the whole array. Mark which vertices are original (in ) versus added LCAs.

// example: cut edges of minimum total weight so that no marked vertex is connected to the root (root not marked)
// mn[v] = minimum edge weight on the root path of v (precomputed once)
ll cutFromRoot(const LCA& L, const vector<ll>& mn, vector<int> S, vector<char>& marked, int root) {
    S.push_back(root);
    auto [vs, vpar] = virtualTree(L, S);
    int k = sz(vs); vector<ll> dp(k); map<int, int> idx; for (int i = 0; i < k; i++) idx[vs[i]] = i;
    for (int i = k - 1; i > 0; i--) {                      // vs[0] is the root (smallest tin)
        int v = vs[i];
        dp[i] = marked[v] ? mn[v] : min(mn[v], dp[i]);    // dp[i] held the children's sum
        dp[idx[vpar[i]]] += dp[i];
    }
    return dp[0];                                          // the root cannot be cut above: sum of children
}

All of these share the same instinct: replace a big structure with a small one that keeps the one property the query needs.

TechniqueCompressesPreserves
Virtual treea vertex subsetancestor relationships
CondensationSCCsreachability
Bridge tree2-edge-connected componentsedge connectivity
Block-cut treebiconnected componentsvertex connectivity
Centroid decompositionthe whole treepath structure, depth

Typical uses

Query on a vertex setVirtual-tree DP
minimum weight to separate all marked vertices from the rootthe DP above
sum / min / max of pairwise distances among marked verticesedge contributions with marked counts; two best depths
number of vertices on the union of paths between marked vertices (Steiner tree size)sum of virtual edge lengths + 1; equals half the cyclic tour over sorted
minimum vertices to delete so marked ones are pairwise disconnectedgreedy DP counting marked children
for each marked vertex, the nearest other marked vertextwo passes (down then up) on the virtual tree