IOI 2009
IOI 2009

Regions

Sqrt Decomposition Let B = N. A region is large if it has B nodes, small otherwise. There are at most N large regions. Case 1: r_1 is large. Precompute via DFS: maintain a counter of ancestors in r_1. At each node u o...

Updated May 21, 2026
Track IOI
Year 2009
Statement Rendered from TeX
TeXC++Rendered statement

Problem Statement

Rendered from the "Problem Statement" section in the LaTeX write-up.

Given a rooted tree of $N$ employees, each in one of $R$ regions. For $Q$ queries $(r_1, r_2)$, count pairs $(e_1, e_2)$ where $e_1$ is a proper ancestor of $e_2$, with $e_1 \in r_1$ and $e_2 \in r_2$.

Editorial

The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.

Solution

Sqrt Decomposition

Let $B = \sqrt{N}$. A region is large if it has $\ge B$ nodes, small otherwise. There are at most $\sqrt{N}$ large regions.

\textbf{Case 1: $r_1$ is large.} Precompute via DFS: maintain a counter of ancestors in $r_1$. At each node $u$ of region $r_2$, add the counter value to $\text{ans}[r_2]$. Total precomputation: $O(N \sqrt{N})$.

\textbf{Case 2: $r_2$ is large.} Precompute $\text{subtreeR2}[u]$ = number of descendants of $u$ in $r_2$ (post-order traversal). For each node $u$ in region $r_1$, add $\text{subtreeR2}[u]$ (excluding $u$ itself if $u \in r_2$, but since $r_1 \ne r_2$ or a node is in one region, this is automatic).

Case 3: Both small. For each node $u$ in $r_1$, binary-search on the sorted Euler-tour times of $r_2$ to count how many $r_2$-nodes have $\text{tin}$ in $[\text{tin}[u]+1, \text{tout}[u]]$. Total work: $O(|r_1| \cdot \log |r_2|)$.

Complexity

  • Precomputation: $O(N\sqrt{N})$.

  • Per query: $O(1)$ for large-region lookups; $O(|r_1| \log |r_2|)$ for small-small (bounded by $O(B \log B) = O(\sqrt{N} \log N)$).

  • Space: $O(N\sqrt{N})$.

C++ Solution

#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, R, Q;
    cin >> N >> R >> Q;

    vector<int> region(N + 1);
    vector<vector<int>> children(N + 1);
    vector<vector<int>> regionNodes(R + 1);

    cin >> region[1];
    regionNodes[region[1]].push_back(1);

    for(int i = 2; i <= N; i++){
        int p;
        cin >> p >> region[i];
        children[p].push_back(i);
        regionNodes[region[i]].push_back(i);
    }

    // Euler tour (iterative DFS)
    vector<int> tin(N + 1), tout(N + 1), euler;
    int timer = 0;
    {
        stack<pair<int,bool>> stk;
        stk.push({1, false});
        while(!stk.empty()){
            auto [u, leaving] = stk.top(); stk.pop();
            if(leaving){ tout[u] = timer - 1; continue; }
            tin[u] = timer++;
            euler.push_back(u);
            stk.push({u, true});
            for(int i = (int)children[u].size()-1; i >= 0; i--)
                stk.push({children[u][i], false});
        }
    }

    int B = max(1, (int)sqrt(N));

    // Sort region nodes by tin
    for(int r = 1; r <= R; r++)
        sort(regionNodes[r].begin(), regionNodes[r].end(),
             [&](int a, int b){ return tin[a] < tin[b]; });

    // Identify large regions
    vector<int> largeId(R + 1, -1);
    vector<int> largeRegions;
    for(int r = 1; r <= R; r++)
        if((int)regionNodes[r].size() >= B){
            largeId[r] = largeRegions.size();
            largeRegions.push_back(r);
        }
    int nLarge = largeRegions.size();

    // Precompute: large r1 -> all r2
    vector<vector<long long>> ansLargeR1(nLarge, vector<long long>(R + 1, 0));
    for(int li = 0; li < nLarge; li++){
        int r1 = largeRegions[li];
        int curCount = 0;
        // DFS maintaining ancestor count in r1
        stack<pair<int,bool>> stk;
        stk.push({1, false});
        while(!stk.empty()){
            auto [u, leaving] = stk.top(); stk.pop();
            if(leaving){
                if(region[u] == r1) curCount--;
                continue;
            }
            if(region[u] == r1) curCount++;
            // u has curCount ancestors in r1 (including itself if in r1)
            // We want strict ancestors, so subtract 1 if u is in r1
            ansLargeR1[li][region[u]] += curCount - (region[u] == r1 ? 1 : 0);
            stk.push({u, true});
            for(int i = (int)children[u].size()-1; i >= 0; i--)
                stk.push({children[u][i], false});
        }
    }

    // Precompute: all r1 -> large r2
    vector<vector<long long>> ansLargeR2(R + 1, vector<long long>(nLarge, 0));
    for(int li = 0; li < nLarge; li++){
        int r2 = largeRegions[li];
        // Compute subtreeR2[u] using reverse euler order
        vector<int> sub(N + 1, 0);
        for(int i = (int)euler.size()-1; i >= 0; i--){
            int u = euler[i];
            sub[u] = (region[u] == r2) ? 1 : 0;
            for(int c : children[u]) sub[u] += sub[c];
        }
        for(int u = 1; u <= N; u++){
            int cnt = sub[u] - (region[u] == r2 ? 1 : 0);
            if(cnt > 0) ansLargeR2[region[u]][li] += cnt;
        }
    }

    // Count nodes of region r with tin in [lo, hi]
    auto countInRange = [&](int r, int lo, int hi) -> int {
        auto& v = regionNodes[r];
        auto it_lo = lower_bound(v.begin(), v.end(), lo,
            [&](int node, int val){ return tin[node] < val; });
        auto it_hi = upper_bound(v.begin(), v.end(), hi,
            [&](int val, int node){ return val < tin[node]; });
        return (int)(it_hi - it_lo);
    };

    while(Q--){
        int r1, r2;
        cin >> r1 >> r2;
        long long ans = 0;

        if(largeId[r1] >= 0){
            ans = ansLargeR1[largeId[r1]][r2];
        } else if(largeId[r2] >= 0){
            ans = ansLargeR2[r1][largeId[r2]];
        } else {
            for(int u : regionNodes[r1])
                ans += countInRange(r2, tin[u] + 1, tout[u]);
        }
        cout << ans << "\n";
        cout.flush();
    }
    return 0;
}

Notes

The sqrt decomposition is essential for handling online queries efficiently. The three cases (large $r_1$, large $r_2$, both small) each exploit different structural properties. The Euler-tour representation converts the ancestor-descendant relationship to a range query, enabling binary search on sorted entry times.

Code

C++ solution used for this page.

C++

Clean code view with a raw-file link when you want the original source.

Raw file
// IOI 2009 - Regions
// Sqrt decomposition on region sizes. Large regions precomputed via DFS,
// small regions answered via binary search on Euler-tour order.
// O(N sqrt(N)) precomputation, O(sqrt(N)) per query.
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, R, Q;
    cin >> N >> R >> Q;

    vector<int> region(N + 1);
    vector<vector<int>> children(N + 1);
    vector<vector<int>> regionNodes(R + 1);

    cin >> region[1];
    regionNodes[region[1]].push_back(1);

    for (int i = 2; i <= N; i++) {
        int p;
        cin >> p >> region[i];
        children[p].push_back(i);
        regionNodes[region[i]].push_back(i);
    }

    // Euler tour (iterative DFS).
    vector<int> tin(N + 1), tout(N + 1);
    int timer = 0;
    vector<int> euler;
    {
        stack<pair<int, bool>> stk;
        stk.push({1, false});
        while (!stk.empty()) {
            auto [u, leaving] = stk.top(); stk.pop();
            if (leaving) {
                tout[u] = timer - 1;
                continue;
            }
            tin[u] = timer++;
            euler.push_back(u);
            stk.push({u, true});
            for (int i = (int)children[u].size() - 1; i >= 0; i--) {
                stk.push({children[u][i], false});
            }
        }
    }

    int B = max(1, (int)sqrt(N));

    // Sort each region's node list by entry time.
    for (int r = 1; r <= R; r++) {
        sort(regionNodes[r].begin(), regionNodes[r].end(),
             [&](int a, int b) { return tin[a] < tin[b]; });
    }

    // Identify large regions (size >= B).
    vector<int> largeId(R + 1, -1);
    vector<int> largeRegions;
    for (int r = 1; r <= R; r++) {
        if ((int)regionNodes[r].size() >= B) {
            largeId[r] = (int)largeRegions.size();
            largeRegions.push_back(r);
        }
    }
    int nLarge = (int)largeRegions.size();

    // --- Precompute for large r1 (as ancestor region) ---
    // ansLargeR1[li][r2] = number of (e1, e2) pairs where e1 in r1 is ancestor of e2 in r2.
    vector<vector<long long>> ansLargeR1(nLarge, vector<long long>(R + 1, 0));
    for (int li = 0; li < nLarge; li++) {
        int r1 = largeRegions[li];
        struct Frame { int u; bool entered; };
        stack<Frame> dfs;
        dfs.push({1, false});
        int curCount = 0; // number of ancestors in r1 on the current root-to-node path
        while (!dfs.empty()) {
            auto &f = dfs.top();
            if (!f.entered) {
                f.entered = true;
                if (region[f.u] == r1) curCount++;
                // Strict ancestor: subtract 1 if node itself is in r1.
                int ancestorsR1 = curCount - (region[f.u] == r1 ? 1 : 0);
                ansLargeR1[li][region[f.u]] += ancestorsR1;
                for (int i = (int)children[f.u].size() - 1; i >= 0; i--) {
                    dfs.push({children[f.u][i], false});
                }
            } else {
                if (region[f.u] == r1) curCount--;
                dfs.pop();
            }
        }
    }

    // --- Precompute for large r2 (as descendant region) ---
    // ansLargeR2[r1][li] = number of pairs where r1-node is ancestor of r2-node.
    vector<vector<long long>> ansLargeR2(R + 1, vector<long long>(nLarge, 0));
    for (int li = 0; li < nLarge; li++) {
        int r2 = largeRegions[li];
        // Compute subtreeR2[u] = number of r2-nodes in subtree of u.
        // Process euler in reverse (children before parents in pre-order reversed).
        vector<int> subtreeR2(N + 1, 0);
        for (int i = (int)euler.size() - 1; i >= 0; i--) {
            int u = euler[i];
            subtreeR2[u] = (region[u] == r2) ? 1 : 0;
            for (int c : children[u]) {
                subtreeR2[u] += subtreeR2[c];
            }
        }
        // For each node u in region r1, it contributes subtreeR2[u] descendants in r2
        // (minus 1 if u itself is in r2, but each node belongs to exactly one region).
        for (int u = 1; u <= N; u++) {
            int cnt = subtreeR2[u] - (region[u] == r2 ? 1 : 0);
            if (cnt > 0) {
                ansLargeR2[region[u]][li] += cnt;
            }
        }
    }

    // Helper: count nodes of region r whose tin is in [lo, hi].
    auto countInRange = [&](int r, int lo, int hi) -> int {
        auto &v = regionNodes[r];
        auto getLo = lower_bound(v.begin(), v.end(), lo,
            [&](int node, int val) { return tin[node] < val; });
        auto getHi = upper_bound(v.begin(), v.end(), hi,
            [&](int val, int node) { return val < tin[node]; });
        return (int)(getHi - getLo);
    };

    // Answer queries.
    while (Q--) {
        int r1, r2;
        cin >> r1 >> r2;

        long long ans = 0;
        if (largeId[r1] >= 0) {
            ans = ansLargeR1[largeId[r1]][r2];
        } else if (largeId[r2] >= 0) {
            ans = ansLargeR2[r1][largeId[r2]];
        } else {
            // Both small: for each node in r1, count descendants in r2.
            for (int u : regionNodes[r1]) {
                ans += countInRange(r2, tin[u] + 1, tout[u]);
            }
        }

        cout << ans << "\n";
        cout.flush();
    }

    return 0;
}

Source Files and Assets

Raw files are still available here when you want the original TeX, C++, or statement assets.

Show raw files