D - 山岳縦走路の最長下り列 / Longest Descent Sequence on a Mountain Traverse Route 解説 by admin
gemini-3-flash-thinkingOverview
This problem asks us to find the length of the Longest Strictly Decreasing Subsequence (LSDS) when we line up the elevation values along the path (shortest route) between two points on a tree structure.
Analysis
1. Rephrasing the Problem
Finding the “Longest Strictly Decreasing Subsequence (LSDS)” is equivalent to finding the “Longest Increasing Subsequence (LIS)” of the sequence where each element’s sign is flipped (i.e., using \(-H_i\)). Since LIS algorithms are generally more familiar, we adopt the approach of negating the elevations and computing the LIS.
2. Identifying the Path on the Tree
In a tree, the path between two points \(u, v\) is the unique route that passes through their Lowest Common Ancestor (LCA). The path can be divided into two parts: 1. Ascending from the starting point \(u\) to the LCA (moving toward the parent) 2. Descending from the LCA to the endpoint \(v\) (moving toward the children)
Since both \(N\) and \(Q\) are relatively small at \(5000\), we can afford to naively trace parents to construct the path for each query within the time limit.
3. Computing the LIS (Longest Increasing Subsequence)
The length of the LIS of a sequence can be efficiently computed using a binary search-based algorithm in \(O(K \log K)\) (where \(K\) is the length of the sequence).
Specifically, we maintain tails[i] as “the minimum possible tail value of an increasing subsequence of length \(i+1\)”, and for each element \(x\) in the sequence, we perform the following operation:
- Use binary search to find the smallest element in tails that is greater than or equal to \(x\), and replace it with \(x\).
- If no element greater than or equal to \(x\) exists, append \(x\) to the end of tails.
The final length of tails is the length of the LIS.
Algorithm
- Preprocessing:
- Convert all elevations \(H_i\) to \(-H_i\).
- Compute the parent \(p_i\) and depth (distance from the root) for each node.
- Query Processing (for each query \(u, v\)):
- Path Construction:
- Compare the depths of \(u\) and \(v\), and trace the deeper one toward its parent until both are at the same depth.
- Once the depths are equal, trace both simultaneously toward their parents until they meet. The meeting point is the LCA.
- During this process, create the elevation list in the order \(u \to \dots \to \text{LCA}\) and the elevation list in the order \(\text{LCA} \to \dots \to v\).
- LIS Computation:
- Apply the LIS algorithm using binary search (
bisect_left) to the elevation sequence of the entire constructed path.
- Apply the LIS algorithm using binary search (
- Output: Record the length of the LIS.
- Path Construction:
Complexity
- Time Complexity: \(O(Q \cdot N \log N)\)
- Per query, extracting the path takes \(O(N)\) and computing the LIS takes \(O(N \log N)\).
- Since \(N, Q \leq 5000\), the worst case involves approximately \(5000 \times 5000 \times \log(5000) \approx 3 \times 10^8\) operations. While this is somewhat heavy for Python, the path length is not always \(N\), and the constant factor is small, so it fits within the time limit.
- Space Complexity: \(O(N)\)
- \(O(N)\) space is used for storing the tree structure, elevations, and the path.
Implementation Notes
Fast I/O: Since \(Q\) can be large, we use
sys.stdin.read().split()andsys.stdout.writeto speed up input and output.Finding the LCA: Due to the constraint \(p_i < i\), we can easily find the LCA by simply tracing parents with node 1 as the root.
Path Concatenation: By concatenating the list from \(u \to \text{LCA}\) with the reversed list from \(v \to \text{LCA}\) (excluding the LCA), we obtain the path in the correct order.
Source Code
import sys
from bisect import bisect_left
def solve():
# Read all input data at once for speed
data = sys.stdin.read().split()
if not data:
return
ptr = 0
N = int(data[ptr])
ptr += 1
Q = int(data[ptr])
ptr += 1
# Pre-negate elevations to find the Longest Strictly Decreasing Subsequence (LSDS)
# as the Longest Strictly Increasing Subsequence (LIS) of the negated elevations.
neg_H = [0] * (N + 1)
for i in range(1, N + 1):
neg_H[i] = -int(data[ptr])
ptr += 1
# Precompute parents and depths. Node 1 is the root.
# The constraint p_i <= i-1 ensures that when we process node i, its parent p_i is already processed.
parent = [0] * (N + 1)
depth = [0] * (N + 1)
for i in range(2, N + 1):
p = int(data[ptr])
ptr += 1
parent[i] = p
depth[i] = depth[p] + 1
# Local references for slightly faster access in tight loops
bisect = bisect_left
results = []
for _ in range(Q):
u = int(data[ptr])
ptr += 1
v = int(data[ptr])
ptr += 1
curr_u, curr_v = u, v
u_path = []
v_path = []
u_path_append = u_path.append
v_path_append = v_path.append
u_depth = depth[curr_u]
v_depth = depth[curr_v]
# Traverse up from u and v to find their Lowest Common Ancestor (LCA)
# while storing elevations along the paths.
while u_depth > v_depth:
u_path_append(neg_H[curr_u])
curr_u = parent[curr_u]
u_depth -= 1
while v_depth > u_depth:
v_path_append(neg_H[curr_v])
curr_v = parent[curr_v]
v_depth -= 1
while curr_u != curr_v:
u_path_append(neg_H[curr_u])
v_path_append(neg_H[curr_v])
curr_u = parent[curr_u]
curr_v = parent[curr_v]
# Add the LCA node elevation to the path from u to LCA
u_path_append(neg_H[curr_u])
# Calculate the LIS length on the sequence: u_path followed by reversed v_path.
# This sequence represents the negated elevations along the path from u to v.
tails = []
tails_append = tails.append
# Process the path from u to LCA
for x in u_path:
idx = bisect(tails, x)
if idx == len(tails):
tails_append(x)
else:
tails[idx] = x
# Process the path from LCA to v (by iterating v_path in reverse)
for j in range(len(v_path) - 1, -1, -1):
x = v_path[j]
idx = bisect(tails, x)
if idx == len(tails):
tails_append(x)
else:
tails[idx] = x
results.append(str(len(tails)))
# Output all results separated by newlines
sys.stdout.write('\n'.join(results) + '\n')
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-thinking.
投稿日時:
最終更新: