D - 街灯の配置 / Placement of Street Lights Editorial by admin
gpt-5.5-highOverview
Sort the positions of the streetlights and consider only the differences between adjacent streetlights.
Create a function that determines “whether the maximum dark interval can be made \(D\) or less,” and find the answer using binary search.
Analysis
Let the streetlight positions sorted in ascending order be
\(A_0, A_1, \ldots, A_{M-1}\)
Define the differences between adjacent streetlights as
\(G_i = A_{i+1} - A_i\)
Then the current maximum dark interval is the maximum value of \(G_i\).
Why a naive approach is difficult
There are \(M\) choices for “which streetlight to move” and up to \(N\) choices for “where to move it.”
If we compute the maximum dark interval after each move, it takes at least \(O(MN)\) in the worst case, which is too slow for \(N,M \leq 3 \times 10^5\).
Determining whether we can achieve \(D\) or less
Instead of directly computing the answer, we determine:
Can we move exactly \(1\) streetlight so that all adjacent differences become \(D\) or less?
If we can achieve \(D\) or less, then we can certainly achieve \(D+1\) or more as well.
Therefore, this determination has monotonicity, so binary search can be used.
Bad gaps
For a fixed \(D\), we call a gap with \(G_i > D\) a “bad gap.”
When moving one streetlight, the operation can be thought of in two stages:
- Remove a streetlight
- Place that streetlight at a different unoccupied position
When a streetlight is removed, only the gaps to its left and right are affected.
For example, if we remove an interior streetlight \(A_i\):
- The two gaps \(A_i - A_{i-1}\) and \(A_{i+1} - A_i\) disappear
- Instead, a new gap \(A_{i+1} - A_{i-1}\) is created
On the other hand, the placement operation can fix at most \(1\) gap.
Therefore, if \(2\) or more bad gaps remain after removal, that \(D\) is impossible.
Also, if there are \(3\) or more bad gaps from the start, it is impossible.
This is because removing \(1\) streetlight cannot reduce the bad gaps sufficiently.
Condition for splitting a bad gap
Suppose a bad gap is \((u, v)\), meaning the left streetlight is at \(u\) and the right streetlight is at \(v\).
To split this gap with a new streetlight \(x\) so that both resulting differences are \(D\) or less:
\(u < x < v\)
and
\(x - u \leq D\)
\(v - x \leq D\)
must hold.
Therefore, the range where \(x\) can be placed is
\(L = \max(u+1, v-D)\)
\(R = \min(v-1, u+D)\)
If there exists an originally unoccupied position within the interval \([L, R]\), then that bad gap can be fixed.
Algorithm
First, perform preprocessing:
- Sort the streetlight positions into array \(A\)
- Compute adjacent differences \(G_i = A_{i+1} - A_i\)
- Create an array indicating whether each position is occupied
- Create a prefix sum
prefso that we can determine in \(O(1)\) whether there is a free position in any interval \([l,r]\)
Whether there is a free position in interval \([l,r]\) can be determined by:
Whether the length of the interval \(r-l+1\) is greater than the number of streetlights in that interval.
That is,
\((r-l+1) > \text{occupied count in } [l,r]\)
means there is a free position.
Feasibility function feasible(D)
Fix \(D\) and check whether it is achievable.
1. Enumerate bad gaps
Collect gaps where \(G_i > D\).
If there are \(3\) or more bad gaps, it is impossible, so immediately return False.
2. Try each streetlight as the “removed streetlight”
Assume streetlight \(A_i\) is removed.
Count the number of bad gaps remaining after removal.
- The left gap \(G_{i-1}\) disappears
- The right gap \(G_i\) disappears
- If it is an interior streetlight, a new gap \(A_{i+1}-A_{i-1}\) is created
Let \(c\) be the number of bad gaps after removal.
3. If \(c > 1\)
Since placing a streetlight can fix at most \(1\) bad gap, it is impossible.
4. If \(c = 1\)
Check whether the single remaining bad gap can be split by the newly placed streetlight.
If the bad gap is \((u,v)\), the placeable range is
\([\max(u+1, v-D), \min(v-1, u+D)]\)
If there is an originally unoccupied position in this interval, it is possible.
5. If \(c = 0\)
At the point of removal, all gaps are \(D\) or less.
In this case, we just need a place to put the streetlight where no new gap exceeds \(D\).
The candidates are as follows:
- To the left of the leftmost remaining streetlight
- To the right of the rightmost remaining streetlight
- Inside an originally existing gap that contains a free position
- Inside the new gap formed by connecting the left and right neighbors of the removed streetlight
For each candidate, use the prefix sum to check whether there is an originally unoccupied position satisfying the conditions.
Binary Search
Since feasible(D) is monotone, we binary search on \(D\).
- If
feasible(D) == True, try a smaller \(D\) - If
feasible(D) == False, try a larger \(D\)
Ultimately, the minimum possible \(D\) is the answer.
Complexity
- Time complexity: \(O(N + M \log M + M \log N)\)
- Sorting takes \(O(M \log M)\)
- Each feasibility check takes \(O(M)\)
- Binary search performs \(O(\log N)\) checks
- Space complexity: \(O(N + M)\)
Implementation Notes
The relocation destination must be an “originally unoccupied position.” Since the removed streetlight cannot be placed back at its original position, we use the prefix sum to accurately determine the presence of free positions.
Whether there is a free position in interval \([l,r]\) can be determined by
(r-l+1) > pref[r] - pref[l-1].The maximum dark interval only considers adjacent differences between streetlights, so distances to the road endpoints \(1,N\) are not considered. However, the relocation destination must be between \(1\) and \(N\) inclusive.
If there are \(3\) or more bad gaps, we can immediately conclude it is impossible. This ensures that at most \(2\) bad gaps need to be handled within the feasibility check.
Source Code
import sys
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
N, M = data[0], data[1]
A = sorted(data[2:])
G = [A[i + 1] - A[i] for i in range(M - 1)]
empty_gap_count = sum(1 for x in G if x >= 2)
occ = bytearray(N + 1)
for x in A:
occ[x] = 1
pref = [0] * (N + 1)
s = 0
for i in range(1, N + 1):
s += occ[i]
pref[i] = s
def feasible(D):
a = A
g = G
pref_local = pref
m = M
n = N
m1 = m - 1
egc = empty_gap_count
bad_idxs = []
for idx, val in enumerate(g):
if val > D:
if len(bad_idxs) == 2:
return False
bad_idxs.append(idx)
C = len(bad_idxs)
bad_split = []
for j in bad_idxs:
u = a[j]
v = a[j + 1]
l = v - D
t = u + 1
if l < t:
l = t
r = u + D
t = v - 1
if r > t:
r = t
bad_split.append(l <= r and (r - l + 1) > (pref_local[r] - pref_local[l - 1]))
for i in range(m):
c = C
if i > 0 and g[i - 1] > D:
c -= 1
if i < m1 and g[i] > D:
c -= 1
hbad = False
if 0 < i < m1:
if a[i + 1] - a[i - 1] > D:
c += 1
hbad = True
if c > 1:
continue
if c == 0:
if i == 0:
first = a[1]
else:
first = a[0]
l = first - D
if l < 1:
l = 1
r = first - 1
if l <= r and (r - l + 1) > (pref_local[r] - pref_local[l - 1]):
return True
if i == m1:
last = a[m - 2]
else:
last = a[m1]
l = last + 1
r = last + D
if r > n:
r = n
if l <= r and (r - l + 1) > (pref_local[r] - pref_local[l - 1]):
return True
cnt = egc
if i > 0 and g[i - 1] >= 2:
cnt -= 1
if i < m1 and g[i] >= 2:
cnt -= 1
if cnt > 0:
return True
if 0 < i < m1:
l = a[i - 1] + 1
r = a[i + 1] - 1
if l <= r and (r - l + 1) > (pref_local[r] - pref_local[l - 1]):
return True
else:
if hbad:
u = a[i - 1]
v = a[i + 1]
l = v - D
t = u + 1
if l < t:
l = t
r = u + D
t = v - 1
if r > t:
r = t
if l <= r and (r - l + 1) > (pref_local[r] - pref_local[l - 1]):
return True
else:
left = i - 1
right = i
for k, j in enumerate(bad_idxs):
if j != left and j != right:
if bad_split[k]:
return True
break
return False
lo, hi = 0, N
while hi - lo > 1:
mid = (lo + hi) // 2
if feasible(mid):
hi = mid
else:
lo = mid
print(hi)
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.5-high.
posted:
last update: