E - 靴下のペアリング / Pairing Socks Editorial by admin
gemini-3.5-flash-thinkingOverview
This problem asks us to form \(M\) pairs from \(N\) given socks and minimize the total difference in color intensity (discomfort) for each pair.
When socks are sorted by color intensity, it is always optimal to pair “adjacent socks.” However, simply greedily selecting pairs in order of smallest difference may not yield the global minimum. Therefore, we use a “regret-based greedy algorithm (reversible greedy)” that allows the operation of “canceling a previously selected pair and choosing different pairs instead (regret).”
Analysis
1. Sorting Socks and Adjacent Element Differences
Intuitively, it is optimal to pair socks with similar color intensities. When we sort the sock array \(S\) in ascending order, optimal pairs always consist of adjacent elements in the sorted array. Therefore, we consider an array of differences between adjacent elements \(D_i = S_{i+1} - S_i\), and the problem reduces to selecting \(M\) elements from this array such that no two selected elements are adjacent, minimizing the total sum (since one sock cannot belong to multiple pairs, adjacent \(D_i\) and \(D_{i+1}\) cannot be selected simultaneously).
2. Failure of Simple Greedy
A simple greedy approach of “selecting \(M\) elements with the smallest \(D_i\) values while ensuring no two are adjacent” fails to find the optimal solution in cases like the following:
- Example: \(S = [1, 10, 11, 20]\) with \(M=2\)
- The adjacent differences are \([9, 1, 9]\).
- The simple greedy first selects the smallest difference, the central \(1\) (pairing socks \(10\) and \(11\)).
- Then, both neighboring \(9\)’s become unavailable, and we are forced to pair socks \(1\) and \(20\). The total is \(1 + 19 = 20\).
- However, the optimal selection is pairs \((1, 10)\) and \((11, 20)\), with a total of \(9 + 9 = 18\).
In this way, because of the constraint that “selecting an element makes both its neighbors unavailable,” a locally optimal choice can prevent reaching the globally optimal solution.
3. Greedy with “Regret”
To solve this problem, we allow the option of “instead of selecting element \(i\), selecting both its neighbors \(i-1\) and \(i+1\)” to be chosen later.
When element \(i\) has already been selected and we cancel it to select both neighbors instead, the “additional cost (regret value)” is: $\(\text{Additional Cost} = D_{i-1} + D_{i+1} - D_i\)$
By inserting this additional cost as a new option into a priority queue (heap), and removing the neighboring elements from the list while relinking, we enable this “regret” option to be selected in subsequent steps.
Algorithm
We simulate using a doubly linked list combined with a priority queue (min-heap).
Initialization:
- Sort the sock array \(S\) in ascending order.
- Compute differences between adjacent elements \(val[i] = S_i - S_{i-1}\) (1-indexed).
- Create arrays
prev_nodeandnext_noderepresenting a doubly linked list, holding the left and right indices of each element. Place sentinels with infinity (\(INF\)) at the ends (indices \(0\) and \(N\)). - Add all \((val[i], i)\) to the priority queue
hq.
Selection Loop (\(M\) iterations):
- Pop the element \((v, idx)\) with the smallest value from
hq. If the index has already been deleted, skip it and pop the next one. - Add \(v\) to the answer.
- Let \(l = prev\_node[idx]\) and \(r = next\_node[idx]\) be the neighboring indices of \(idx\).
- Mark \(idx\), \(l\), and \(r\) as used (deleted).
- Insert regret value: Compute the new value \(new\_val = val[l] + val[r] - v\), assign it to \(val[idx]\), and restore \(idx\) as not deleted.
- Update the linked list by relinking the new neighbors of \(idx\) to \(ll = prev\_node[l]\) and \(rr = next\_node[r]\).
- Add the new option \((new\_val, idx)\) to
hq.
- Pop the element \((v, idx)\) with the smallest value from
Output:
- Print the total value after the \(M\) iterations of the loop.
Complexity
- Time Complexity: \(O(N \log N)\)
- Sorting the socks takes \(O(N \log N)\) time.
- Priority queue operations (pop/push) are performed in \(O(\log N)\) each over \(M\) iterations of the loop, giving \(O(M \log N)\) total. Since \(M \le N/2\), the overall time complexity is \(O(N \log N)\), which is well within the time limit.
- Space Complexity: \(O(N)\)
- The doubly linked list, priority queue, and deletion flag management use \(O(N)\) memory.
Implementation Notes
Array-based Doubly Linked List: Instead of using pointers, we manage predecessor/successor relationships using index arrays
prev_nodeandnext_node, achieving a simple and fast implementation.Sentinel (\(INF\)) Placement: By setting very large values \(INF\) at the array ends (indices \(0\) and \(N\)), when an edge element is selected and we attempt to reference beyond it, the infinite cost is incurred, smartly avoiding special boundary condition handling.
Lazy Deletion: We manage whether elements have been deleted using a
deletedarray, and skip already-deleted elements when popping from the queue. This “lazy deletion” approach avoids the overhead of removing invalid elements from the heap on the spot.Source Code
import sys
import heapq
def solve():
# Fast I/O
input_data = sys.stdin.read().split()
if not input_data:
return
N = int(input_data[0])
M = int(input_data[1])
S = [int(x) for x in input_data[2:]]
S.sort()
INF = 10**16
# Initialize the differences and doubly linked list
val = [0] * (N + 1)
val[0] = INF
val[N] = INF
for i in range(1, N):
val[i] = S[i] - S[i-1]
prev_node = list(range(-1, N))
next_node = list(range(1, N + 2))
next_node[N] = -1
deleted = [False] * (N + 1)
# Priority queue for greedy selection
hq = [(val[i], i) for i in range(1, N)]
heapq.heapify(hq)
ans = 0
for _ in range(M):
while hq:
v, idx = heapq.heappop(hq)
if not deleted[idx]:
break
ans += v
deleted[idx] = True
l = prev_node[idx]
r = next_node[idx]
deleted[l] = True
deleted[r] = True
# Insert the "regret" value to allow backtracking
new_val = val[l] + val[r] - v
val[idx] = new_val
deleted[idx] = False
ll = prev_node[l]
rr = next_node[r]
prev_node[idx] = ll
next_node[idx] = rr
if ll != -1:
next_node[ll] = idx
if rr != -1:
prev_node[rr] = idx
heapq.heappush(hq, (new_val, idx))
print(ans)
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3.5-flash-thinking.