E - 山並みの見晴らし / Mountain Range Vista Editorial by admin
Claude 4.6 Opus (Thinking)Overview
When viewing a mountain range from the west side, the problem asks to determine the number of “visible” mountains within a specified interval for each query. We precompute the transition to the next visible mountain using a monotonic stack, then use doubling (binary lifting) to answer each query efficiently.
Analysis
Rephrasing the “Visible” Condition
Mountain \(k\) is visible in the interval \([L, R]\) means that there is no \(m\) with \(L \le m < k\) such that \(H_m > H_k\). In other words, \(H_k \ge \max(H_L, H_{L+1}, \ldots, H_k)\) holds.
Visible Mountains Form a Chain Structure
Starting from position \(L\) and following the visible mountains in order, we notice that we transition to the next mountain with height greater than or equal to the current mountain.
For example, with \(H = [3, 1, 4, 2, 5]\) starting from \(L=0\): - Mountain 0 (height 3) → The next mountain with height ≥ 3 is mountain 2 (height 4) - Mountain 2 (height 4) → The next mountain with height ≥ 4 is mountain 4 (height 5)
The visible mountains are {0, 2, 4}, totaling 3.
Problem with the Naive Approach
If we scan the interval from left to right for each query, each query takes \(O(N)\) in the worst case, resulting in \(O(NQ)\) overall, which causes TLE.
Solution: Transition Function + Doubling
For each position \(i\), define \(\text{nxt}[i]\) as “the smallest \(j\) to the right of \(i\) satisfying \(H[j] \ge H[i]\)”. Then, the sequence of visible mountains can be traced as \(L \to \text{nxt}[L] \to \text{nxt}[\text{nxt}[L]] \to \cdots\).
By applying doubling to this transition, we can process each query in \(O(\log N)\).
Algorithm
Computing \(\text{nxt}[i]\) (Monotonic Stack)
Scan the array from left to right, maintaining unresolved indices on the stack. If \(H[i]\) is greater than or equal to the value at the top of the stack, set \(\text{nxt}\) of the stack top to \(i\) and pop. This computes everything in \(O(N)\) overall.Building the Doubling Table
\(\text{up}[k][i]\): the position reached from position \(i\) after \(2^k\) transitions.- \(\text{up}[0][i] = \text{nxt}[i]\)
- \(\text{up}[k][i] = \text{up}[k-1][\text{up}[k-1][i]]\) (if reachable)
Answering Queries
For query \((L, R)\), start with \(\text{cur} = L\), \(\text{count} = 1\). Try \(k\) from largest to smallest; if \(\text{up}[k][\text{cur}] \le R\), jump \(2^k\) steps and add that to count. The final count is the answer.
Complexity
- Time complexity: \(O(N \log N + Q \log N)\) (\(O(N \log N)\) for building the doubling table, \(O(\log N)\) per query)
- Space complexity: \(O(N \log N)\) (doubling table)
Implementation Notes
Be careful with the definition of \(\text{nxt}\): Transition using “\(H[j] \ge H[i]\)” (greater than or equal). Including equality is necessary because mountains of the same height are also visible.
Boundary values: Set \(\text{nxt}[i] = N\) (out of array bounds) as default, and in the doubling table, do not transition if the value is \(N\) or greater.
Conversion to 0-indexed: Since the input is 1-indexed, the internal processing converts to 0-indexed.
Setting LOG: \(\lceil \log_2(N+1) \rceil + 1\) is sufficient. Since the maximum number of jumps is \(N-1\), \(\log_2 N\) levels are enough to cover it.
Source Code
import sys
from math import log2, ceil
def main():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
Q = int(input_data[idx]); idx += 1
H = [int(input_data[idx + i]) for i in range(N)]
idx += N
queries = []
for j in range(Q):
L = int(input_data[idx]); idx += 1
R = int(input_data[idx]); idx += 1
queries.append((L - 1, R - 1, j)) # 0-indexed
# For each query [L, R], count the number of "visible" mountains from the left.
# A mountain k is visible if no mountain m with L <= m < k has H[m] > H[k].
# Equivalently, mountain k is visible if H[k] >= max(H[L..k]).
# This equals the number of "left-to-right maxima (non-strict)" in the subarray.
# We use an offline approach with a stack-based structure.
#
# Key idea: Process queries offline sorted by L (right to left).
# We maintain a structure where for each position, we know whether it contributes
# to visibility given a certain starting point.
#
# Alternative: Use a merge sort tree / persistent segment tree approach.
#
# Approach: Offline with sweepline from right to left on L.
# For a fixed L, a position k in [L, R] is visible iff H[k] >= max(H[L..k]).
# As L decreases, the set of visible positions changes.
#
# Let's use a different approach: sparse table for range max, and binary lifting
# to count visible mountains.
#
# For each position i, define nxt[i] = the next position j > i such that H[j] >= H[i]
# (using a monotone stack). Then from L, the visible mountains are L, nxt[L], nxt[nxt[L]], ...
# until we exceed R. We can use binary lifting on the "nxt" function.
# Compute nxt[i]: next index j > i with H[j] >= H[i]
nxt = [N] * N # default: beyond array
stack = []
for i in range(N):
while stack and H[stack[-1]] <= H[i]:
nxt[stack.pop()] = i
stack.append(i)
# Binary lifting on nxt
LOG = max(1, ceil(log2(N + 1)) + 1)
# up[k][i] = position reached after 2^k jumps from i
# cnt is implicit: 2^k jumps means 2^k visible mountains after i
up = [[0] * N for _ in range(LOG)]
up[0] = nxt[:]
for k in range(1, LOG):
for i in range(N):
if up[k-1][i] < N:
up[k][i] = up[k-1][up[k-1][i]]
else:
up[k][i] = N
# For query (L, R): start at L (which is always visible, count=1),
# then jump via nxt. Count how many jumps land within [L, R].
# The answer = 1 + number of jumps from L that land in <= R.
ans = [0] * Q
for L, R, j in queries:
count = 1 # L itself is visible
cur = L
for k in range(LOG - 1, -1, -1):
if up[k][cur] <= R:
count += (1 << k)
cur = up[k][cur]
ans[j] = count
sys.stdout.write('\n'.join(map(str, ans)) + '\n')
main()
This editorial was generated by claude4.6opus-thinking.
posted:
last update: