E - 石飛びの小道 / Stepping Stones Path Editorial by admin Editorial for printing
Claude 4.6 Opus (Thinking)Overview
This problem asks us to maximize the total score of visited stepping stones when moving from stone \(1\) to stone \(N\), using at most \(K\) skips (jumping over one stone). It can be reduced to the classic problem of “selecting at most \(K\) non-adjacent elements to maximize their sum.”
Analysis
Key Insight ①: A skip means “jumping over one stone”
If no skips are used, all stones are visited, so the total score is \(\sum_{i=1}^{N} A_i\). Using a skip allows you to jump over exactly one stone. If stone \(j\) is skipped, \(A_j\) is subtracted from the total score (i.e., \(-A_j\) is added).
Key Insight ②: Two adjacent stones cannot be skipped simultaneously
Skipping stone \(j\) means jumping from \(j-1\) to \(j+1\). If you also want to skip \(j+1\), you would need to jump from \(j\) to \(j+2\), but since \(j\) itself was not visited, this is impossible. Therefore, two adjacent stones cannot be skipped simultaneously.
Problem Reformulation
Since stones \(1\) and \(N\) must always be visited, the skip candidates are stones \(2, 3, \ldots, N-1\). Defining the “gain” of skipping stone \(j\) as \(B_j = -A_j\):
Select at most \(K\) non-adjacent elements from array \(B\) and maximize their sum.
Naive DP Results in TLE
If we define \(dp[i][k]\) = “maximum sum when selecting \(k\) elements from the first \(i\) elements,” this gives \(O(NK)\), but since both \(N\) and \(K\) can be up to \(2 \times 10^5\), the worst case is \(O(N^2)\), which results in TLE.
Algorithm
We use a greedy approach with a heap and a doubly linked list. This is a classic technique that solves “selecting \(K\) non-adjacent elements with maximum sum” in \(O(N \log N)\).
Procedure
- Manage each element with a doubly linked list and insert values into a max-heap.
- Extract the element \(idx\) with the maximum value from the heap. If the value is \(\leq 0\), terminate.
- Add its value to the total gain.
- Merge operation: Mark the left neighbor \(p\) and right neighbor \(n\) of element \(idx\) as “used,” and update the value of \(idx\) as follows: $\(\text{val}[idx] \leftarrow \text{val}[p] + \text{val}[n] - \text{val}[idx]\)\( This new value represents the differential gain of "undoing the selection of \)idx\( and instead selecting both \)p\( and \)n$.”
- Push the updated \(idx\) back into the heap and re-link the linked list.
- Repeat until \(K\) selections are made or no positive gain remains.
Concrete Example
For \(A = [3, -5, -8, 4, 7]\), \(K = 2\): - Total of all stones = \(3 + (-5) + (-8) + 4 + 7 = 1\) - Skip candidates: stones 2, 3, 4 → gains \(B = [5, 8, -4]\) - Round 1: Select \(B[1]=8\) (skip stone 3) → gain \(+8\), after merge \(B[1] = 5+(-4)-8 = -7\) - Round 2: Select \(B[0]=5\) (skip stone 2) → gain \(+5\) - Total: \(1 + 8 + 5 = 14\) (skip stones 2 and 3…?)
Note here: \(B[0]\) and \(B[1]\) are adjacent, but due to the merge operation, “selecting the new \(B[1]=-7\) after having selected \(B[1]\)” is equivalent to “undoing the selection of \(B[1]\) and selecting \(B[0]\) and \(B[2]\) instead.” The algorithm correctly maintains the non-adjacency constraint.
Complexity
- Time complexity: \(O(N \log N)\) (dominated by heap operations)
- Space complexity: \(O(N)\) (for the linked list and heap)
Implementation Notes
removed array: Check whether an element extracted from the heap has already been eliminated by a merge (lazy deletion).
Linked list boundary handling: The first and last elements may not have left or right neighbors, so conditional branching is necessary.
New value during merge: The update formula \(\text{val}[p] + \text{val}[n] - \text{val}[idx]\) is the core part that correctly represents “undo + alternative selection.”
Early termination: If the gain becomes \(\leq 0\), further skips only cause losses, so terminate immediately.
Source Code
import sys
def solve():
input_data = sys.stdin.buffer.read().split()
N = int(input_data[0])
K = int(input_data[1])
A = [int(input_data[i + 2]) for i in range(N)]
if N <= 2:
print(A[0] + A[N - 1] if N == 2 else A[0])
return
# dp[i][k] = max score to reach stone i using exactly k skips
# This is O(N*K) which could be up to 2*10^5 * 2*10^5 = 4*10^10, too large.
# Better approach: Think about which stones to skip.
# Without any skips, we visit all stones and get sum(A).
# Each skip skips exactly one stone (stone i+1 when jumping from i to i+2).
# But skips can't overlap in certain ways.
#
# When we skip stone j (2 <= j <= N-1, since we must visit 1 and N),
# we save A[j] (i.e., subtract A[j] from the total if A[j] is negative, that helps).
# Actually, skipping stone j means we DON'T collect A[j], so the gain from skipping j is -A[j].
# We want to maximize total, so we want to skip stones with the most negative A[j] values,
# i.e., skip stones where -A[j] is largest, i.e., A[j] is most negative.
#
# BUT: constraint is that we can't skip two adjacent stones. If we skip stone j,
# we jump from j-1 to j+1. If we also skip stone j+1, we'd need to jump from j-1 to j+2,
# but that's a jump of 3, which isn't allowed. So no two consecutive stones can be skipped.
#
# Also, stones 1 and N cannot be skipped.
#
# So the problem reduces to: select at most K non-adjacent stones from {2, 3, ..., N-1}
# to skip, maximizing the sum of (-A[j]) for skipped stones j.
# Equivalently: select at most K non-adjacent elements from array B where B[j] = -A[j+1]
# for j = 0..N-3 (representing stones 2..N-1), maximize sum of selected elements.
# Total without skipping = sum(A)
# We add the sum of (-A[j]) for each skipped stone j.
total = sum(A)
# Stones that can be skipped: indices 1 to N-2 (0-indexed), i.e., stones 2 to N-1
# Values to pick from: B[i] = -A[i+1] for i in 0..N-3... wait let me reindex.
# Skippable stones (0-indexed): 1, 2, ..., N-2
# Gain from skipping stone i (0-indexed): -A[i]
# Select at most K non-adjacent from these, maximize sum of gains.
B = [-A[i] for i in range(1, N - 1)] # gains for skipping stones 1..N-2 (0-indexed)
M = len(B) # = N - 2
if M == 0 or K == 0:
print(total)
return
# Now: select at most K non-adjacent elements from B to maximize their sum.
# We only want to skip stones that give positive gain (B[i] > 0), unless forced.
# Since K is "at most", we never need to skip a stone with non-positive gain.
# This is the classic "select at most K non-adjacent elements with maximum sum" problem.
# Can be solved with DP: dp[i][j] = max sum using first i elements with j selected.
# O(M*K) might be too large.
# Use the greedy algorithm with a heap (similar to the "select K non-adjacent with max sum"
# problem solvable in O(M log M) using a linked list + heap approach).
# Actually, let's just do DP with optimization. K can be at most (N-2+1)/2 ~ N/2 effectively.
# But O(N*K) can be 2*10^10 in worst case.
# Let's use the heap-based greedy approach.
import heapq
# Greedy: repeatedly pick the best available element, then merge neighbors.
# This is the classic algorithm for selecting K non-adjacent elements with max sum.
INF = float('inf')
prev_node = list(range(-1, M - 1))
next_node = list(range(1, M + 1))
val = list(B)
removed = [False] * M
heap = [(-B[i], i) for i in range(M)]
heapq.heapify(heap)
gain = 0
selected = 0
while selected < K and heap:
neg_v, idx = heapq.heappop(heap)
if removed[idx]:
continue
v = -neg_v
if v <= 0:
break
gain += v
selected += 1
p = prev_node[idx]
n = next_node[idx]
new_val = -v
if p >= 0:
new_val += val[p]
pp = prev_node[p]
removed[p] = True
if pp >= 0:
next_node[pp] = idx
prev_node[idx] = pp
else:
prev_node[idx] = -1
if n < M:
new_val += val[n]
nn = next_node[n]
removed[n] = True
if nn < M:
prev_node[nn] = idx
next_node[idx] = nn
else:
next_node[idx] = M
val[idx] = new_val
heapq.heappush(heap, (-new_val, idx))
print(total + gain)
solve()
This editorial was generated by claude4.6opus-thinking.
posted:
last update: