E - 展示作品の選定 / Selection of Exhibited Works 解説 by admin
Gemini 3.1 Pro (Thinking)Overview
Given a sequence of numbers, the problem asks to find the length of the longest subsequence (a sequence formed by selecting elements in order) such that the absolute difference between adjacent elements is at most \(D\).
Analysis
First, let’s consider a naive dynamic programming (DP) approach.
Define dp[i] as “the maximum number of elements that can be selected when the \(i\)-th element is chosen last.”
Then, the following recurrence holds:
\(dp[i] = \max(dp[j]) + 1\) (where \(j < i\) and \(|H_i - H_j| \leq D\))
However, this method searches through all \(j\) satisfying the condition for each \(i\), resulting in \(O(N^2)\) time complexity. Given the constraint \(N \leq 2 \times 10^5\), this would exceed the time limit (TLE).
To speed this up, we consider changing the DP state from “index \(i\)” to “evaluation score \(H_i\) of the element.” When selecting the \(i\)-th element (with score \(H_i\)), the score \(H_j\) of the previously selected element must satisfy: \(H_i - D \leq H_j \leq H_i + D\) In other words, we need to efficiently find “the maximum DP value among all previously seen elements whose score is between \(H_i - D\) and \(H_i + D\).”
This can be solved using a Segment Tree, a data structure that efficiently supports “range maximum queries” and “point updates.” However, since the score \(H_i\) can be up to \(10^9\), using it directly as an array index would exceed memory limits. Therefore, we also apply a technique called coordinate compression, which extracts only the score values that appear in the input and reassigns them to smaller indices.
Algorithm
Coordinate Compression Create an array
valsby extracting all distinct evaluation scores from \(H\) and sorting them in ascending order. This converts scores that could be up to \(10^9\) into indices ranging from \(0\) to at most \(N-1\).Segment Tree Initialization Prepare a segment tree that manages range maximum values. The size should be a power of \(2\) that is at least the number of elements in
vals. All initial values are \(0\).DP Computation Process the elements from the 1st to the last in order. Let \(h\) be the score of the current element.
- The score range to search is \([h - D, h + D]\).
- Using binary search (
bisect_left,bisect_right), determine which index range \([l, r]\) invalscorresponds to this score range. - Use the segment tree to obtain the maximum value in the range \([l, r]\). Adding \(1\) to this maximum gives
dp, the maximum count when selecting the current element. - Update the overall answer with
dp. - Update the position in the segment tree corresponding to \(h\) (at index
bisect_left(vals, h)) with the larger of the current value anddp.
Output the Answer After processing all elements, output the recorded maximum value.
Complexity
- Time complexity: \(O(N \log N)\)
- Sorting for coordinate compression takes \(O(N \log N)\).
- For each element, binary search and segment tree query/update each take \(O(\log N)\), and this is done \(N\) times, giving \(O(N \log N)\).
- Overall, the complexity is \(O(N \log N)\), which is sufficiently fast.
- Space complexity: \(O(N)\)
- Memory proportional to \(N\) is used for the coordinate compression array and the segment tree array.
Implementation Notes
Non-recursive Segment Tree: In Python, function call overhead is significant, so recursive segment tree implementations can be slow. Using a bottom-up (non-recursive) implementation with a 1D array and
whileloops, as in the solution code, results in faster execution.Checking for Empty Ranges: In the range \([l, r]\) obtained by binary search, there may be cases where no scores satisfy the condition (i.e., \(l > r\)). In such cases, instead of querying the segment tree, the maximum value should be treated as \(0\).
Source Code
import sys
from bisect import bisect_left, bisect_right
def solve():
input = sys.stdin.read
data = input().split()
if not data:
return
N = int(data[0])
D = int(data[1])
H = [int(x) for x in data[2:]]
vals = sorted(list(set(H)))
M = len(vals)
size = 1
while size < M:
size *= 2
tree = [0] * (2 * size)
def query(l, r):
l += size
r += size
res = 0
while l <= r:
if l % 2 == 1:
if tree[l] > res:
res = tree[l]
l += 1
if r % 2 == 0:
if tree[r] > res:
res = tree[r]
r -= 1
l //= 2
r //= 2
return res
def update(pos, value):
pos += size
if value > tree[pos]:
tree[pos] = value
pos //= 2
while pos > 0:
left_child = tree[2 * pos]
right_child = tree[2 * pos + 1]
tree[pos] = left_child if left_child > right_child else right_child
pos //= 2
ans = 0
for h in H:
l = bisect_left(vals, h - D)
r = bisect_right(vals, h + D) - 1
if l <= r:
max_val = query(l, r)
else:
max_val = 0
dp = max_val + 1
if dp > ans:
ans = dp
pos = bisect_left(vals, h)
update(pos, dp)
print(ans)
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3.1-pro-thinking.
投稿日時:
最終更新: