Official

J - 道路ネットワークの整備 / Road Network Development Editorial by admin

Claude 4.6 Opus (Thinking)

Overview

This is a problem where, on a rooted tree, you perform \(Q\) updates of “+1 to all edges on a path,” and then answer \(R\) queries asking for “the minimum edge weight on a path.”

Analysis

Issues with the Naive Approach

If we perform path updates naively, a single update may modify up to \(O(N)\) edges, resulting in \(O(NQ)\) total time. Similarly, naively traversing the path for each query takes \(O(NR)\). Since \(N, Q, R \leq 5 \times 10^4\), we need to optimize each operation.

Key Insights

Efficient Updates: Difference Array (Euler Tour + Difference)

The operation of uniformly adding values along a path on a tree can be efficiently handled using a difference array on nodes. The number of additions to edge \(i\) (connecting city \(i\) to its parent) equals the sum of the difference values of all nodes in the subtree of city \(i\).

Specifically, for a path \(u \to v\) (with LCA \(l\)): - \(\text{diff}[u] += 1\) - \(\text{diff}[v] += 1\) - \(\text{diff}[l] -= 2\)

After recording this, we compute the subtree sums bottom-up, and \(\text{diff}[i]\) becomes the number of times edge \(i\) was incremented.

Efficient Queries: Minimum Value via Binary Lifting (Doubling)

The minimum value query on a path can be processed in \(O(\log N)\) using path minimum via binary lifting with LCA.

Algorithm

Step 1: Preprocessing (Binary Lifting for LCA)

  • Compute the depth \(\text{depth}[i]\) of each node using BFS
  • Binary lifting table \(\text{up}[k][i]\): the ancestor reached by going up \(2^k\) times from node \(i\)

Step 2: Path Updates (Difference Array)

For each update \((u_j, v_j)\): 1. Find the LCA \(l = \text{lca}(u_j, v_j)\) 2. \(\text{diff}[u_j] += 1,\ \text{diff}[v_j] += 1,\ \text{diff}[l] -= 2\)

After all updates, aggregate in reverse BFS order (leaves → root) as \(\text{diff}[\text{parent}] += \text{diff}[\text{child}]\), and \(\text{diff}[i]\) becomes the number of additions to edge \(i\).

Final weight of edge \(i\): \(\text{edge\_w}[i] = W_i + \text{diff}[i]\)

Step 3: Query Answering (Path Minimum via Binary Lifting)

  • \(\text{up\_min}[k][i]\): the minimum edge weight encountered when traversing \(2^k\) edges upward from node \(i\)
  • Base case: \(\text{up\_min}[0][i] = \text{edge\_w}[i]\)
  • Transition: \(\text{up\_min}[k][i] = \min(\text{up\_min}[k-1][i],\ \text{up\_min}[k-1][\text{up}[k-1][i]])\)

For a query \((a, b)\), split the path into two parts \(a \to \text{LCA}\) and \(b \to \text{LCA}\) (using the same technique as LCA computation), and collect the minimum using binary lifting.

Complexity

  • Time complexity: \(O((N + Q + R) \log N)\)
    • Preprocessing (building binary lifting tables): \(O(N \log N)\)
    • LCA computation for each update: \(O(Q \log N)\)
    • Aggregation of differences: \(O(N)\)
    • Answering each query: \(O(R \log N)\)
  • Space complexity: \(O(N \log N)\) (for two binary lifting tables)

Implementation Notes

  • Difference array trick: Record edge additions as “\(-2\) at LCA, \(+1\) at both endpoints” on nodes, and recover the number of additions to each edge via subtree sums. This is also known as the imos method on trees.

  • Bottom-up aggregation: Processing in reverse BFS order correctly aggregates from leaves to root (use iteration instead of recursion to avoid stack overflow).

  • \(\text{up}[k][0] = 0\) (the root has no parent, so set it to \(0\)): this prevents out-of-bounds access when leveling depths during LCA computation.

  • Since the answer is at most \(2^{63}-1\), 64-bit integers (automatically arbitrary precision in Python) can handle it without issues.

    Source Code

import sys
from math import log2

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    
    N = int(input_data[idx]); idx += 1
    
    parent = [0] * (N + 1)
    W = [0] * (N + 1)  # W[i] = weight of edge i (edge between i and parent[i])
    children = [[] for _ in range(N + 1)]
    
    for i in range(2, N + 1):
        p = int(input_data[idx]); idx += 1
        w = int(input_data[idx]); idx += 1
        parent[i] = p
        W[i] = w
        children[p].append(i)
    
    # BFS to compute depth and euler tour
    depth = [0] * (N + 1)
    order = []
    stack = [1]
    visited = [False] * (N + 1)
    visited[1] = True
    # Iterative BFS
    from collections import deque
    q = deque([1])
    while q:
        u = q.popleft()
        order.append(u)
        for c in children[u]:
            depth[c] = depth[u] + 1
            visited[c] = True
            q.append(c)
    
    # Binary lifting for LCA
    LOG = max(1, (N).bit_length())
    up = [[0] * (N + 1) for _ in range(LOG)]
    for i in range(1, N + 1):
        up[0][i] = parent[i]
    for k in range(1, LOG):
        for i in range(1, N + 1):
            up[k][i] = up[k-1][up[k-1][i]]
    
    def lca(u, v):
        if depth[u] < depth[v]:
            u, v = v, u
        diff = depth[u] - depth[v]
        for k in range(LOG):
            if (diff >> k) & 1:
                u = up[k][u]
        if u == v:
            return u
        for k in range(LOG - 1, -1, -1):
            if up[k][u] != up[k][v]:
                u = up[k][u]
                v = up[k][v]
        return up[0][u]
    
    # Process updates: for each update (u, v), add 1 to all edges on path u->v
    # Use difference array on nodes: for edge i (connecting i to parent[i]),
    # the edge weight increase = diff[i] where diff is accumulated.
    # For path u->v with LCA l: diff[u] += 1, diff[v] += 1, diff[l] -= 2
    
    diff = [0] * (N + 1)
    
    Q = int(input_data[idx]); idx += 1
    for _ in range(Q):
        u = int(input_data[idx]); idx += 1
        v = int(input_data[idx]); idx += 1
        if u == v:
            continue
        l = lca(u, v)
        diff[u] += 1
        diff[v] += 1
        diff[l] -= 2
    
    # Compute subtree sums in reverse BFS order (bottom-up)
    for node in reversed(order):
        for c in children[node]:
            diff[node] += diff[c]
    
    # Now diff[i] for i>=2 is the number of times edge i was incremented
    # Final weight of edge i = W[i] + diff[i]
    edge_w = [0] * (N + 1)
    for i in range(2, N + 1):
        edge_w[i] = W[i] + diff[i]
    
    # Now answer queries: for each query (a, b), find min edge weight on path a->b
    # With N, R up to 5*10^4, we can use binary lifting with sparse table for min
    # up_min[k][i] = min edge weight on the path of 2^k edges going up from i
    
    up_min = [[float('inf')] * (N + 1) for _ in range(LOG)]
    for i in range(1, N + 1):
        if parent[i] != 0:
            up_min[0][i] = edge_w[i]
        else:
            up_min[0][i] = float('inf')
    for k in range(1, LOG):
        for i in range(1, N + 1):
            up_min[k][i] = min(up_min[k-1][i], up_min[k-1][up[k-1][i]])
    
    def query_min(u, v):
        if u == v:
            return 0
        res = float('inf')
        if depth[u] < depth[v]:
            u, v = v, u
        diff_d = depth[u] - depth[v]
        for k in range(LOG):
            if (diff_d >> k) & 1:
                res = min(res, up_min[k][u])
                u = up[k][u]
        if u == v:
            return res
        for k in range(LOG - 1, -1, -1):
            if up[k][u] != up[k][v]:
                res = min(res, up_min[k][u], up_min[k][v])
                u = up[k][u]
                v = up[k][v]
        res = min(res, up_min[0][u], up_min[0][v])
        return res
    
    R = int(input_data[idx]); idx += 1
    out = []
    for _ in range(R):
        a = int(input_data[idx]); idx += 1
        b = int(input_data[idx]); idx += 1
        out.append(str(query_min(a, b)))
    
    sys.stdout.write('\n'.join(out) + '\n')

main()

This editorial was generated by claude4.6opus-thinking.

posted:
last update: