Official

D - 山岳縦走路の最長下り列 / Longest Descent Sequence on a Mountain Traverse Route Editorial by admin

Qwen3-Coder-480B

Overview

This problem asks us to answer queries that find the length of the longest strictly decreasing subsequence of the elevation sequence along the path between two given points on a tree structure.

Analysis

In this problem, we need to extract the path between any two points on a tree and find the “Longest Decreasing Subsequence” of the elevation sequence along that path.

Naive Approach and Its Issues

A naive approach would process each query as follows: 1. Find the path from the starting point \(u\) to the ending point \(v\) using BFS or DFS, 2. Extract the elevation sequence along that path, 3. Compute the longest strictly decreasing subsequence of that sequence using dynamic programming or similar methods.

However, this requires \(O(N)\) computation per query in the worst case, resulting in \(O(QN)\) overall, which does not fit within the time limit under the given constraints.

Improvement: LCA and Fast Computation of Longest Decreasing Subsequence

We apply the following optimizations: - Use the Lowest Common Ancestor (LCA) to efficiently find paths on the tree. - When computing the longest strictly decreasing subsequence from the elevation sequence along the path, we use a greedy approach + binary search (a variation of LIS) for speedup.

LCA can be computed with \(O(N \log N)\) preprocessing and \(O(\log N)\) per query, and the longest decreasing subsequence can be computed in \(O(k \log k)\) where \(k\) is the length of the sequence.

This allows us to achieve a sufficiently fast algorithm overall.

Algorithm

  1. Tree Construction and Preprocessing

    • Build the tree using an adjacency list.
    • Compute the depth and parent of each vertex from the root (vertex 1) using DFS/BFS.
    • Preprocess LCA using Binary Lifting (\(O(N \log N)\)).
  2. Processing Each Query

    • For a query with starting point \(u\) and ending point \(v\), find the LCA \(l\).
    • To construct the path \(u \to l \to v\):
      • Traverse from \(u\) to \(l\) by following parent pointers,
      • Traverse from \(v\) to \(l\) by following parent pointers, then reverse and concatenate.
    • Compute the length of the longest strictly decreasing subsequence on the elevation sequence of the obtained path:
      • Use the greedy + binary search algorithm (reverse version of LIS).
      • Handle each element by negating it, and manage insertion positions using bisect.

Complexity

  • Time complexity: \(O(N \log N + Q \cdot L \log L)\)
    (where \(L\) is the path length; in the worst case \(L = O(N)\))
  • Space complexity: \(O(N \log N)\)

Implementation Notes

  • When constructing the tree, create the adjacency list as an undirected graph.

  • The Binary Lifting table for LCA, up[i][k], stores the ancestor \(2^k\) levels above vertex \(i\).

  • Make sure to include the LCA itself when constructing the path.

  • When computing the longest decreasing subsequence, unlike the standard LIS, we need “strictly decreasing,” so duplicates must not be allowed during insertion.

  • By using bisect.bisect_left, we can greedily select the subsequence with the smallest tail element.

    Source Code

import sys
from collections import deque
import bisect

sys.setrecursionlimit(100000)

def main():
    import sys
    input = sys.stdin.read
    data = input().split()
    
    idx = 0
    N = int(data[idx]); idx += 1
    Q = int(data[idx]); idx += 1
    
    H = [0] * (N + 1)
    for i in range(1, N + 1):
        H[i] = int(data[idx]); idx += 1
    
    if N == 1:
        parents = []
    else:
        parents = [0] * (N + 1)
        for i in range(2, N + 1):
            parents[i] = int(data[idx]); idx += 1

    # Build adjacency list
    adj = [[] for _ in range(N + 1)]
    for i in range(2, N + 1):
        u = i
        v = parents[i]
        adj[u].append(v)
        adj[v].append(u)
    
    # Preprocessing: parent/depth/ancestors for LCA
    root = 1
    depth = [-1] * (N + 1)
    parent_list = [0] * (N + 1)
    order = []
    
    stack = [root]
    depth[root] = 0
    parent_list[root] = -1
    while stack:
        u = stack.pop()
        order.append(u)
        for v in adj[u]:
            if depth[v] == -1:
                depth[v] = depth[u] + 1
                parent_list[v] = u
                stack.append(v)
    
    # Binary lifting for LCA
    LOG = 16
    up = [[-1] * LOG for _ in range(N + 1)]
    for i in range(1, N + 1):
        up[i][0] = parent_list[i]
    for k in range(1, LOG):
        for i in range(1, N + 1):
            if up[i][k - 1] != -1:
                up[i][k] = up[up[i][k - 1]][k - 1]
    
    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 & (1 << k):
                u = up[u][k]
        if u == v:
            return u
        for k in range(LOG - 1, -1, -1):
            if up[u][k] != up[v][k]:
                u = up[u][k]
                v = up[v][k]
        return up[u][0]
    
    # Path from u to v: u -> lca -> v
    def get_path(u, v):
        l = lca(u, v)
        path = []
        x = u
        while x != l:
            path.append(x)
            x = parent_list[x]
        path.append(l)
        tmp = []
        x = v
        while x != l:
            tmp.append(x)
            x = parent_list[x]
        path.extend(tmp[::-1])
        return path

    results = []
    for _ in range(Q):
        u = int(data[idx]); idx += 1
        v = int(data[idx]); idx += 1
        path = get_path(u, v)
        heights = [H[node] for node in path]
        
        # Compute LIS in decreasing order using greedy with binary search
        # We maintain the smallest tail of all decreasing subsequences of length i+1
        tails = []
        for h in heights:
            pos = bisect.bisect_left(tails, -h)
            if pos == len(tails):
                tails.append(-h)
            else:
                tails[pos] = -h
        results.append(len(tails))
    
    print('\n'.join(map(str, results)))

if __name__ == "__main__":
    main()

This editorial was generated by qwen3-coder-480b.

posted:
last update: