D - 電波塔の設置 / Installation of Radio Towers Editorial by admin
Claude 4.5 OpusOverview
This is a problem where villages are arranged in a straight line, radio towers can only be installed in villages with altitude \(K\) or higher, and each radio tower can cover villages within distance \(D\). We need to find the minimum number of radio towers required to cover all villages.
Analysis
Key Observations
Sort villages by position: Since villages are arranged along a single road, sorting them by position allows us to think in terms of “covering from left to right.”
Greedy approach is effective: When processing villages that are not yet covered from the left end, it is optimal to install a tower at the rightmost village among the tower candidates that can cover that village. This is because installing further to the right has the potential to simultaneously cover more villages on the right side.
Coverage condition: A radio tower at position \(x\) can cover all villages in the range \([x - D, x + D]\). Conversely, to cover a village at position \(t\), a radio tower in the range \([t - D, t + D]\) is needed.
Problem with Naive Approach
Checking all tower candidates for each village results in \(O(N^2)\) complexity, which would cause TLE for \(N = 2 \times 10^5\).
Solution
- Maintain indices of tower candidates and scan in position order
- Efficiently search for candidates that can cover “the leftmost uncovered village”
- Use binary search to quickly find the right endpoint of the range covered by a radio tower
Algorithm
Preprocessing
- Sort villages in ascending order of position \(X_i\)
- Extract indices of villages satisfying altitude \(P_i \geq K\) as
tower_candidates - If candidates are empty, output
-1and terminate
Greedy Coverage
covered_until: Maximum index of covered villages (initial value -1)tc_idx: Starting index for tower candidate search
Repeat the following:
- Identify the leftmost uncovered village (index covered_until + 1)
- Select the rightmost one among tower candidates that can cover this village (position within \([target\_pos - D, target\_pos + D]\))
- If no such candidate exists, output -1
- Find the right endpoint of the range covered by the selected tower using binary search and update covered_until
- Advance tc_idx appropriately for the next search
- Termination Condition
- Once all villages are covered, output the number of installed radio towers
Complexity
Time Complexity: \(O(N \log N)\)
- Sorting: \(O(N \log N)\)
- Greedy loop: Each tower candidate is scanned at most about 2 times (two-pointer-like movement), so \(O(N)\)
- Binary search: \(O(\log N)\) per tower installation
Space Complexity: \(O(N)\)
- Storage for village information, position array, and tower candidate list
Implementation Notes
Importance of sorting: Input villages are not necessarily in position order, so always sort by position before processing.
Updating tc_idx: Once the next village to cover is determined, candidates that cannot cover it (position less than \(target\_pos - D\)) can be skipped. This keeps the overall complexity low.
Using binary search: Use
bisect.bisect_rightto quickly find the rightmost village within the range \([tower\_pos - D, tower\_pos + D]\) covered by a tower.Impossibility check: Output
-1if there are no villages where a tower can be installed, or if no tower candidate can cover a certain village.
Source Code
def solve():
import sys
input_data = sys.stdin.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
K = int(input_data[idx]); idx += 1
D = int(input_data[idx]); idx += 1
villages = []
for i in range(N):
X = int(input_data[idx]); idx += 1
P = int(input_data[idx]); idx += 1
villages.append((X, P))
# Sort by position
villages.sort(key=lambda x: x[0])
# Indices of villages where towers can be installed (in position order)
tower_candidates = []
for i, (x, p) in enumerate(villages):
if p >= K:
tower_candidates.append(i)
if not tower_candidates:
# No village where a tower can be installed
print(-1)
return
# Solve using greedy approach
# From left to right, we need to cover the leftmost uncovered village
# Among tower candidates that can cover that village, install at the rightmost one
n = len(villages)
count = 0
covered_until = -1 # Villages up to index covered_until are covered (-1 means nothing is covered)
# Position of each village
positions = [v[0] for v in villages]
# tower_candidates is in position order (since villages are sorted by position)
tc_idx = 0 # Current index in tower_candidates
while covered_until < n - 1:
# Leftmost uncovered village
target_village_idx = covered_until + 1
target_pos = positions[target_village_idx]
# Find a tower that can cover this village
# Within distance D from tower position x → |target_pos - x| <= D
# That is, target_pos - D <= x <= target_pos + D
# Among tower candidates, select the one that can cover target_pos and is rightmost
# Reason for selecting rightmost: it has the potential to cover more villages on the right
best_tower_idx = -1
best_tower_pos = -1
# Start search from tc_idx
# Since tower candidates are in position order, stop when exceeding target_pos + D
for i in range(tc_idx, len(tower_candidates)):
ti = tower_candidates[i]
tx = positions[ti]
if tx > target_pos + D:
# All subsequent candidates cannot cover target_pos
break
if tx >= target_pos - D:
# This tower can cover target_pos
if tx > best_tower_pos:
best_tower_idx = ti
best_tower_pos = tx
if best_tower_idx == -1:
# No tower can cover target_village
print(-1)
return
# Install tower at best_tower_idx
count += 1
# Range this tower can cover: best_tower_pos - D <= x <= best_tower_pos + D
# Find the maximum index of covered villages
# Use binary search to find the village with maximum position <= best_tower_pos + D
import bisect
# Maximum i such that positions[i] <= best_tower_pos + D
max_covered_idx = bisect.bisect_right(positions, best_tower_pos + D) - 1
covered_until = max_covered_idx
# Update tc_idx for next iteration (start search from candidates after best_tower_pos)
# However, the next target_village is covered_until + 1, so
# a tower that can cover that village might be to the left of best_tower_pos
# So we need to be careful when updating tc_idx
#
# Actually, to cover the next target, we need a tower at position >= target_pos - D
# Since next target_pos >= best_tower_pos - D + 1 (worst case),
# the required tower position is >= target_pos - D
#
# For simplicity, don't reset tc_idx, search from the beginning as needed
# But for efficiency, candidates before current best_tower_idx are often unnecessary
# Set tc_idx to the first candidate with position >= best_tower_pos - D
# However, depending on the next target, earlier ones might be needed
# For safety, don't reset, but skip clearly unnecessary ones
# Next target is covered_until + 1 = max_covered_idx + 1
# Its position is positions[max_covered_idx + 1] (if it exists)
# To cover that target, we need a tower at position >= positions[max_covered_idx + 1] - D
#
# Update tc_idx: advance while positions[tower_candidates[tc_idx]] < next_target_pos - D
if covered_until < n - 1:
next_target_pos = positions[covered_until + 1]
while tc_idx < len(tower_candidates) and positions[tower_candidates[tc_idx]] < next_target_pos - D:
tc_idx += 1
print(count)
solve()
This editorial was generated by claude4.5opus.
posted:
last update: