公式

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

DeepSeek V3

Overview

This problem asks you to find the length of the longest strictly decreasing subsequence from the elevation sequence along the path between two points on a tree.

Analysis

The problem requires finding the length of the Longest Strictly Decreasing Subsequence (LSD) from the elevation sequence on the path between two points in a tree structure. The key observations are as follows:

  1. The tree structure is given as a rooted tree, and the path between any two points is uniquely determined.
  2. To find the path, we need to find the Lowest Common Ancestor (LCA) of the two points.
  3. The longest strictly decreasing subsequence is the reverse version of the Longest Increasing Subsequence (LIS) problem, and can be efficiently solved using a similar dynamic programming + binary search approach (\(O(k \log k)\) time, where \(k\) is the path length).

As a naive approach, if we retrieve the path for each query and perform a simple DP on the elevation sequence of that path, the worst case would be \(O(Q \times k^2)\) time, which would result in TLE given the constraints where \(N, Q\) can be up to 5000. However, the LSD problem has a well-known efficient solution (greedy + binary search), allowing each query to be processed in \(O(k \log k)\).

Algorithm

  1. Preprocessing:
    • Compute the depth of each node (root has depth 0, each node’s depth is parent’s depth + 1).
    • Adopt a simple method for finding the LCA (equalize depths, then climb up until reaching the common ancestor).
  2. Query Processing:
    • For each query \((u, v)\), find the LCA \(w\).
    • Concatenate the path from \(u\) to \(w\) (following parents from \(u\) to \(w\)) and the path from \(w\) to \(v\) (following parents from \(v\) to \(w\), but in reverse order) to obtain the full path.
    • Find the length of the longest strictly decreasing subsequence (LSD) on the elevation sequence of the path.
  3. LSD Computation:
    • Use greedy + binary search. Maintain an array tails that tracks the tail elements of subsequences.
    • For each elevation \(x\), use binary search to find the largest value in tails that is smaller than \(x\) (i.e., tails is maintained as a decreasing sequence), and place \(x\) at the appropriate position. This yields the length of the longest decreasing subsequence.

Complexity

  • Time complexity: \(O(Q \times (L + k \log k))\). Here \(L\) is the time to compute the LCA (maximum depth is \(O(N)\)), and \(k\) is the path length. In the worst case \(k = O(N)\), so this becomes \(O(Q \times (N + N \log N)) = O(Q N \log N)\). With the constraints \(N, Q \leq 5000\), this is approximately \(5000 \times 5000 \times \log(5000) \approx 5000 \times 5000 \times 13 \approx 3.25 \times 10^8\), which is tight for C++ and similar languages, but can barely work in Python with an optimized implementation.
  • Space complexity: \(O(N)\) (for storing depths and parents during preprocessing).

Implementation Notes

  • Increase recursion depth: Use sys.setrecursionlimit(10000) to relax the recursion limit (since the tree depth can be up to 5000).

  • Path construction: After finding the LCA, separately obtain the path from \(u\) to the LCA and from the LCA to \(v\), then concatenate them.

  • LSD computation: To maintain the tails array as a decreasing sequence, during binary search we search the left side when tails[mid] > x (since in a decreasing sequence, larger values are on the left). This correctly finds the position where \(x\) should be placed.

    Source Code

import sys
sys.setrecursionlimit(10000)

def main():
    data = sys.stdin.read().split()
    if not data:
        return
    
    it = iter(data)
    N = int(next(it)); Q = int(next(it))
    H = [0] * (N+1)
    for i in range(1, N+1):
        H[i] = int(next(it))
    
    parent = [0] * (N+1)
    for i in range(2, N+1):
        p = int(next(it))
        parent[i] = p
    
    queries = []
    for i in range(Q):
        u = int(next(it)); v = int(next(it))
        queries.append((u, v))
    
    depth = [0] * (N+1)
    depth[1] = 0
    for i in range(2, N+1):
        depth[i] = depth[parent[i]] + 1
    
    def lca(u, v):
        while depth[u] > depth[v]:
            u = parent[u]
        while depth[v] > depth[u]:
            v = parent[v]
        while u != v:
            u = parent[u]
            v = parent[v]
        return u
    
    def get_path(u, v):
        w = lca(u, v)
        path1 = []
        while u != w:
            path1.append(u)
            u = parent[u]
        path1.append(w)
        path2 = []
        while v != w:
            path2.append(v)
            v = parent[v]
        path2.reverse()
        return path1 + path2
    
    def lis_decreasing(arr):
        if not arr:
            return 0
        tails = []
        for x in arr:
            left, right = 0, len(tails)
            while left < right:
                mid = (left + right) // 2
                if tails[mid] > x:
                    left = mid + 1
                else:
                    right = mid
            if left == len(tails):
                tails.append(x)
            else:
                tails[left] = x
        return len(tails)
    
    results = []
    for u, v in queries:
        path = get_path(u, v)
        arr = [H[node] for node in path]
        res = lis_decreasing(arr)
        results.append(str(res))
    
    sys.stdout.write("\n".join(results))

if __name__ == "__main__":
    main()

This editorial was generated by deepseekv3.

投稿日時:
最終更新: