Bài 17.3. Phân Chia Đội Thi Đấu - [Độ khó: Khó]
Bài 17.3. Phân Chia Đội Thi Đấu - [Độ khó: Khó]
Trong một cuộc thi lập trình đồng đội sắp tới, có N
thí sinh đăng ký. Mỗi thí sinh có một "điểm năng lực" (Skill Point) là một số nguyên dương. Ban tổ chức muốn chia các thí sinh thành các cặp đôi (hai người mỗi cặp) để tham gia vòng đấu loại. Mục tiêu là tối thiểu hóa tổng "độ chênh lệch năng lực" của tất cả các cặp đôi. Độ chênh lệch năng lực của một cặp đôi là giá trị tuyệt đối của hiệu điểm năng lực giữa hai thí sinh trong cặp. Nếu N
là số lẻ, một thí sinh sẽ bị loại và không được ghép cặp. Bạn cần tìm tổng độ chênh lệch năng lực nhỏ nhất có thể.
INPUT FORMAT
Dòng đầu tiên chứa một số nguyên dương N
(1 <= N
<= 10^5) - số lượng thí sinh.
Dòng thứ hai chứa N
số nguyên S_1, S_2, ..., S_N
(1 <= S_i
<= 10^9) - điểm năng lực của từng thí sinh, cách nhau bởi dấu cách.
OUTPUT FORMAT
In ra một số nguyên duy nhất là tổng độ chênh lệch năng lực nhỏ nhất có thể.
Ví dụ:
Input:
5
10 20 15 25 5
Output:
10
Giải thích:
Các điểm năng lực là: {10, 20, 15, 25, 5}. Số lượng thí sinh N = 5
là số lẻ, vậy sẽ có 1 thí sinh bị loại.
Bước 1: Sắp xếp các điểm năng lực theo thứ tự tăng dần: {5, 10, 15, 20, 25}.
Bước 2: Để tối thiểu hóa tổng độ chênh lệch, quan sát thấy rằng nếu ta ghép các thí sinh có điểm năng lực gần nhau, độ chênh lệch sẽ nhỏ. Điều này dẫn đến ý tưởng tham lam: sau khi sắp xếp, luôn ghép các thí sinh kề nhau.
Có N
thí sinh. Nếu N
là số lẻ, ta cần bỏ đi một thí sinh.
Giả sử ta bỏ đi thí sinh có điểm X
. Sau khi bỏ X
, ta còn N-1
thí sinh. N-1
là số chẵn.
Khi đó, ta sẽ ghép (N-1)/2
cặp.
Hãy xem xét các trường hợp thí sinh bị loại:
- Nếu bỏ 5: Còn {10, 15, 20, 25}. Ghép (10,15) và (20,25). Tổng chênh lệch = |10-15| + |20-25| = 5 + 5 = 10.
- Nếu bỏ 10: Còn {5, 15, 20, 25}. Ghép (5,15) và (20,25). Tổng chênh lệch = |5-15| + |20-25| = 10 + 5 = 15.
- Nếu bỏ 15: Còn {5, 10, 20, 25}. Ghép (5,10) và (20,25). Tổng chênh lệch = |5-10| + |20-25| = 5 + 5 = 10.
- Nếu bỏ 20: Còn {5, 10, 15, 25}. Ghép (5,10) và (15,25). Tổng chênh lệch = |5-10| + |15-25| = 5 + 10 = 15.
- Nếu bỏ 25: Còn {5, 10, 15, 20}. Ghép (5,10) và (15,20). Tổng chênh lệch = |5-10| + |15-20| = 5 + 5 = 10.
Kết quả nhỏ nhất là 10.
Tổng quát: Để có tổng độ chênh lệch nhỏ nhất, sau khi sắp xếp mảng S
tăng dần, ta nên ghép cặp các phần tử kề nhau: (S[0], S[1]), (S[2], S[3]), ...
.
Nếu N
là số chẵn, tổng độ chênh lệch là (S[1]-S[0]) + (S[3]-S[2]) + ... + (S[N-1]-S[N-2])
.
Nếu N
là số lẻ, ta phải bỏ đi một phần tử. Để tối ưu, ta thử bỏ đi từng phần tử một. Tuy nhiên, cách tối ưu nhất là nhận ra rằng việc bỏ đi một phần tử S[i]
sẽ chia mảng thành hai phần (hoặc giữ nguyên nếu S[i]
là phần tử cuối/đầu). Sau khi bỏ S[i]
, phần còn lại sẽ được ghép cặp kề nhau.
Thực ra, tổng chênh lệch của N
số đã sắp xếp khi ghép kề nhau (S[1]-S[0]) + (S[2]-S[1]) + ... + (S[N-1]-S[N-2])
chính là S[N-1] - S[0]
.
Khi bỏ 1 số, ta sẽ tách 1 cặp ra. Ví dụ, nếu bỏ S[i]
, thì cặp (S[i-1], S[i])
và (S[i], S[i+1])
sẽ bị phá vỡ. S[i-1]
sẽ ghép với S[i+1]
tạo thành (S[i+1]-S[i-1])
. Điều này tương đương với việc mất đi (S[i]-S[i-1]) + (S[i+1]-S[i])
và thêm (S[i+1]-S[i-1])
. Do (S[i+1]-S[i-1]) = (S[i]-S[i-1]) + (S[i+1]-S[i])
, nên nếu bỏ một số bất kì, tổng chênh lệch của các cặp kề nhau không đổi.
Vậy, nếu N lẻ, ta chỉ cần bỏ một số, sau đó áp dụng chiến lược ghép kề nhau. Số lượng cặp sẽ là (N-1)/2
.
Với ví dụ trên: {5, 10, 15, 20, 25}.
Các cặp kề nhau: (5,10), (10,15), (15,20), (20,25).
Tổng chênh lệch là: 5 + 5 + 5 + 5 = 20.
Nếu bỏ một số, ta sẽ mất đi 2 phần tử từ tổng đó, và thêm 1 phần tử (đoạn nối lại).
Ví dụ, bỏ 15. Ta có {5, 10, _, 20, 25}. Thì (10,15) và (15,20) bị phá vỡ. Phần tử 10 và 20 sẽ được ghép. Thay vì |10-15| + |15-20| = 5+5=10, ta có |10-20|=10. Tổng vẫn không đổi.
Vậy, ý tưởng cho N lẻ là tìm tổng của các cặp kề nhau, và "trừ đi" chi phí lớn nhất của một cặp kề nhau?
Không, cách đúng là: Tổng độ chênh lệch nhỏ nhất khi ghép N-1 người thành (N-1)/2 cặp là tổng S[i+1]-S[i]
cho tất cả các cặp, bỏ qua 1 cặp (tương ứng với 1 người bị loại).
Ví dụ: {5, 10, 15, 20, 25}
Các hiệu giữa các phần tử kề nhau là: 10-5=5
, 15-10=5
, 20-15=5
, 25-20=5
.
Nếu N chẵn, tổng chênh lệch sẽ là (S[1]-S[0]) + (S[3]-S[2]) + ...
.
Với N lẻ, ta phải bỏ một số.
Tổng (S[1]-S[0]) + (S[2]-S[1]) + ... + (S[N-1]-S[N-2])
là S[N-1]-S[0]
.
Khi N là lẻ, ta có (N-1)/2
cặp. Để có tổng min, ta phải ghép N-1
phần tử sao cho tổng hiệu là nhỏ nhất. Điều này vẫn là ghép kề nhau.
Có N
số, ta tạo N-1
khoảng d_i = S_{i+1} - S_i
.
Nếu N chẵn, ta chọn N/2
cặp. Có 2 cách chọn:
(S_0,S_1), (S_2,S_3), ...
-> sumd_0 + d_2 + ...
(S_1,S_2), (S_3,S_4), ...
-> sumd_1 + d_3 + ...
Tổng nhỏ nhất làmin(d_0+d_2+..., d_1+d_3+...)
? Không phải.
Thực tế, bài này là một dạng "Dynamic Programming on a sorted array" hoặc một quan sát toán học tinh tế hơn.
Nếu N chẵn, sau khi sắp xếp, tổng chênh lệch nhỏ nhất là sum(S[2i+1] - S[2i])
for i=0 to N/2-1
.
Nếu N lẻ, ta phải bỏ đi một người. Ta có thể bỏ đi S[i]
. Sau đó, ta sẽ ghép S[0], ..., S[i-1], S[i+1], ..., S[N-1]
thành cặp.
Quan trọng là, khi bỏ một người, ta có thể "gián tiếp" phá vỡ hai khoảng kề nhau và tạo một khoảng lớn hơn.
Ví dụ: 5, 10, 15, 20, 25.
Nếu bỏ 15: {5, 10, 20, 25}. Ghép (5,10), (20,25). Sum = 5 + 5 = 10.
Điều này tương đương với việc từ dãy (S_0, S_1), (S_1, S_2), (S_2, S_3), (S_3, S_4)
ta bỏ đi hai đoạn (S_1, S_2)
và (S_2, S_3)
và thay thế bằng (S_1, S_3)
.
|S_1-S_0| + |S_3-S_2| + |S_4-S_3|
.
Thực chất, tổng hiệu của các cặp kề nhau (S[2i+1]-S[2i])
là tối ưu khi N
chẵn.
Khi N
lẻ, ta phải bỏ một S_k
.
Consider the differences d_i = S_{i+1} - S_i
.
If N is odd, we want to choose (N-1)/2
pairs.
The minimum sum of differences is always achieved by matching adjacent elements.
If N is odd, we have N
numbers. We need to leave one out.
Let's sort the numbers: s_0, s_1, s_2, ..., s_{N-1}
.
The total sum of differences between consecutive elements is (s_1-s_0) + (s_2-s_1) + ... + (s_{N-1}-s_{N-2}) = s_{N-1} - s_0
.
If we want to form (N-1)/2
pairs, we essentially are picking (N-1)/2
disjoint intervals.
This specific problem requires you to calculate all N
possible sums, where you remove one person, and then calculate the sum of adjacent differences for the remaining N-1
people.
For N
up to 10^5
, calculating N
times sum over N-1
elements is O(N^2)
, too slow.
A key observation:
Sum of differences for pairs (i, i+1): |S[1]-S[0]| + |S[3]-S[2]| + ...
If we remove S[k]
, then S[k-1]
and S[k+1]
become adjacent.
This problem can be solved by Dynamic Programming or by a clever observation about the total sum of differences.
The minimum total difference is achieved by sorting the array and then summing s[2i+1] - s[2i]
.
If N is odd, one element must be left out. The optimal strategy is to remove one element, and then pair up the remaining N-1
elements greedily.
The remaining N-1
elements will be s_0, ..., s_{k-1}, s_{k+1}, ..., s_{N-1}
.
The cost for them is (s_1-s_0) + ... + (s_{k-1} - s_{k-2}) + (s_{k+1} - s_{k-1}) + (s_{k+3} - s_{k+2}) + ...
This is actually an interesting variant. The example output is 10 for {5, 10, 15, 20, 25}.
This implies we removed 5, 15 or 25.
If we remove 5: {10, 15, 20, 25} -> (10,15), (20,25) -> 5+5=10.
If we remove 15: {5, 10, 20, 25} -> (5,10), (20,25) -> 5+5=10.
If we remove 25: {5, 10, 15, 20} -> (5,10), (15,20) -> 5+5=10.
The core idea for N odd is:
- Sort the array:
S_0, S_1, ..., S_{N-1}
. - Initialize
min_total_diff = infinity
. - For each
i
from0
toN-1
(representing the element to be removed): a. Construct a temporary arraytemp_S
by removingS_i
. b. Calculate the sum of differences for adjacent pairs intemp_S
.sum_diff = (temp_S[1]-temp_S[0]) + (temp_S[3]-temp_S[2]) + ...
c. Updatemin_total_diff = min(min_total_diff, sum_diff)
. This is still O(N^2) if done naively.
A better way for N odd:
Calculate total_sum_adj_diff = (S[1]-S[0]) + (S[2]-S[1]) + ... + (S[N-1]-S[N-2])
. This is S[N-1]-S[0]
.
Now, if we remove S[i]
, we effectively remove (S[i]-S[i-1])
and (S[i+1]-S[i])
from this sum, and add (S[i+1]-S[i-1])
.
No, this is for chain.
The pairs are disjoint: (S[0], S[1]), (S[2], S[3]), ...
If N is odd, we are looking for (N-1)/2
pairs.
The solution to this specific problem (often called "Minimum Absolute Difference Sum in K Pairs") for N odd is to iterate through potential 'gaps' or 'skipped' elements.
The cost if we skip S[i]
is S[N-1] - S[0] - (S[i] - S[i-1])
if i
is an even index, or something similar. This means it becomes S[N-1]-S[0]
minus the largest gap.
This problem is a specific application of sorting and greedy on a sorted array, often related to finding optimal pairing/partitioning. For N odd, the solution is NOT simply picking adjacent elements after removing one. The real solution:
- Sort the array.
- If
N
is even, the answer issum(S[2i+1] - S[2i])
fori=0 to N/2-1
. - If
N
is odd, we need to leave one element out. This is a DP problem:dp[i]
= minimum cost for firsti
elements.dp[i] = min(dp[i-2] + (S[i-1]-S[i-2]), dp[i-1])
ifi
is odd.dp[i] = dp[i-1]
ifi
is even. This seems more like "partition into pairs with minimum sum" or similar. Let's stick to the simplest interpretation that matches the example and is within "medium-hard": Sort the array. If N is even, pair (S[0],S[1]), (S[2],S[3]), ... Sum differences. If N is odd, we must skip one. The optimal skip is either the first element, last element, or one of the elements that causes a minimal increase in sum if its adjacent elements are paired. This is a standard problem: after sorting, if N is odd, iterate on removing each elementS[i]
, and calculate the sum of differences of remaining pairs. It can be solved in O(N) after sorting, by maintaining prefix and suffix sums of differences.prefix[i]
= sum of(S[2j+1]-S[2j])
up to indexi
.suffix[i]
= sum of(S[2j+1]-S[2j])
from indexi
to end. The minimum cost would bemin(prefix[i] + suffix[i+1])
after appropriate handling of skipped elements. This is quite hard for "Medium" chapter unless "Hard" allows it. Given this is "Hard" within the chapter, it might fit.
Let's simplify the explanation of the solution approach for N odd to avoid revealing too much but still guiding.
The core idea is that after sorting, elements that are close in value should be paired. For N
odd, we must choose one element to remove. The key is to see which removal strategy leads to the minimum sum of differences for the remaining N-1
elements paired greedily.
Revised approach for N odd:
- Sort the skill points.
- If
N
is even, pair(S[0], S[1]), (S[2], S[3]), ...
. Calculate sum of differences. - If
N
is odd, this becomes a bit tricky. We need to leave one person out. The optimal way to form pairs fromN-1
people is also to group adjacent elements after the chosen person is removed. To avoidO(N^2)
: Calculate the sum of|S[i] - S[i-1]|
for alli=1
toN-1
. This isS[N-1] - S[0]
. Then iterate throughk
from0
toN-1
as the element to remove. For eachk
: Calculate the sum of differences ifS[k]
is removed. This meansS[k-1]
andS[k+1]
(if they exist) would now become adjacent. This sum can be computed efficiently. Consider the total "adjacency cost" of the sorted array asCost_Total = sum_{i=0 to (N/2)-1} (S_{2i+1} - S_{2i})
. IfN
is even, this is the answer. IfN
is odd, we want to leave one elementS_k
out. This meansS_{k-1}
andS_{k+1}
become adjacent. The example {5, 10, 15, 20, 25} -> 10.N=5
(odd). Sorted: 5, 10, 15, 20, 25. Pairs (if N were even): (5,10), (15,20). Sum: 5+5=10. This is exactly the total sum of(S[2i+1]-S[2i])
for the remaining elements. This means for N odd, we should calculate(S[1]-S[0]) + (S[3]-S[2]) + ...
and try removing each element. This implies the answer ismin(sum_diff_after_removing_S_i)
for alli
. This isO(N)
ifS[k]
is removed, compute sum forS[0]...S[k-1]
andS[k+1]...S[N-1]
. TheO(N)
method involves calculating prefix sumsP[i] = (S[1]-S[0]) + (S[3]-S[2]) + ...
up toS[i]
. And suffix sumsSuff[i] = (S[N-1]-S[N-2]) + (S[N-3]-S[N-4]) + ...
starting fromS[i]
. Then the answer ismin(P[k-1] + Suff[k+1])
+ a(S[k+1]-S[k-1])
ifk-1
andk+1
connect.
The simplest approach for N odd, which is indeed hard enough:
- Sort the skill points.
- If
N
is even: calculate sum ofS[2i+1] - S[2i]
fori=0
toN/2-1
. - If
N
is odd: Initializemin_diff_sum = infinity
. Iteratei
from0
toN-1
: Create a temporary vectortemp_skills
. Copy all elements fromskills
exceptskills[i]
. Calculatecurrent_diff_sum = 0
. Forj
from0
to(N-1)/2 - 1
:current_diff_sum += temp_skills[2j+1] - temp_skills[2j]
.min_diff_sum = min(min_diff_sum, current_diff_sum)
. Returnmin_diff_sum
. This naive O(N^2) would be too slow for N=10^5. TheO(N)
solution for N odd is indeed tricky and often uses prefix/suffix sums of even/odd indexed differences, which is a common DP technique after sorting. This is definitely "Khó".
// Example for N=5, skills {10, 20, 15, 25, 5} -> {5, 10, 15, 20, 25}
// Prefixes for even indexed pairs (0,1), (2,3), ...
// Suffixes for even indexed pairs (..., N-2, N-1)
// Let sorted array be A.
// If A[k] is removed:
// The pairs before k are (A[0], A[1]), (A[2], A[3]), ...
// The pairs after k are (A[k+1], A[k+2]), (A[k+3], A[k+4]), ...
// If k is even (0, 2, 4, ...):
// The pair (A[k-1], A[k]) is removed. (A[k], A[k+1]) is removed.
// Example: remove A[0] (5). Remaining: {10, 15, 20, 25}. Pairs (10,15), (20,25). Sum 5+5=10.
// Example: remove A[2] (15). Remaining: {5, 10, 20, 25}. Pairs (5,10), (20,25). Sum 5+5=10.
// If k is odd (1, 3, 5, ...):
// Example: remove A[1] (10). Remaining: {5, 15, 20, 25}. Pairs (5,15), (20,25). Sum 10+5=15.
// Example: remove A[3] (20). Remaining: {5, 10, 15, 25}. Pairs (5,10), (15,25). Sum 5+10=15.
// So, the output 10 means we either removed 5, 15, or 25.
// This requires computing sum(A[2i+1]-A[2i]) on the remaining N-1 elements.
// This is exactly what prefix/suffix sums allow in O(N).
// Let prefix_even_diff[i] = (A[1]-A[0]) + (A[3]-A[2]) + ... + (A[2j+1]-A[2j]) where 2j+1 <= i.
// Let prefix_odd_diff[i] = (A[2]-A[1]) + (A[4]-A[3]) + ... + (A[2j]-A[2j-1]) where 2j <= i.
// Then calculate suffix_even_diff and suffix_odd_diff similarly.
// When A[k] is removed:
// if k is even: sum = prefix_even_diff[k-1] + suffix_even_diff[k+1] + (A[k+1]-A[k-1]) if k-1, k+1 exist
// if k is odd: sum = prefix_even_diff[k-2] + (A[k]-A[k-2]) + suffix_even_diff[k+1]
// This is actually not that complex to formulate the prefix/suffix sums for "Hard".
Let's assume the "Khó" level is about understanding how to optimize this specific problem with O(N)
solution using prefix/suffix sums on a sorted array after the initial O(N log N)
sort. The explanation in the example will imply this.
Comments