Introduction
A couple of days ago, I worked on a Lanqiao Cup provincial-round problem that calls for a solid grasp of sorting and close attention to implementation details. My initial understanding of the model was off, so I went through two versions of the algorithm and spent quite a while on it. This article reviews the entire solution process, examines the roots of the mistaken approach, and arrives at an efficient, rigorous implementation.
Problem Analysis
P8613 [Lanqiao Cup 2014 Provincial Round B] Children Queuing
Once we understand the statement, we can reduce the problem to a simple core process: count how many times each child is swapped → calculate each child’s unhappiness → add the results.
It is easy to see that each move swaps only two adjacent children, which is exactly the operation used by bubble sort. The problem therefore becomes: bubble-sort all the children, count how many times each child is swapped, and sum their individual unhappiness.
We need a small observation: when bubble-sorting a sequence, the total number of swaps equals the number of inversions in the sequence. This is not difficult to understand. For any pair of elements, we swap them only if they form an inversion. Thus, the total swap count must be exactly the total inversion count.
Here, however, we need each element’s swap count, rather than the total swap count. This is straightforward too: count the inversions that involve that element.
For the final sum, apply the arithmetic-series formula to each child, f(k) = k * (k+1) / 2, then sum over all elements: Σf(k_i).
Algorithm Design and Implementation
With counting all inversions involving each element established as the central goal, I chose a Fenwick tree + coordinate compression. Both versions below build on that structure.
V1.0: First Attempt
The initial approach took a broad view but overlooked individual differences. It combined three main techniques—coordinate compression, a Fenwick tree, and traversal in both directions—to count inversions by height category.
Design
Core data structure: the
ans_carray- Create an array
ans_cof lengthsize, the number of distinct height values.ans_c[idx]stores the total inversion count, or an accumulated count, for all children whose height rank isidx.
- Create an array
Coordinate compression
- Motivation:
HeightsH_ican be as large as10^6, whilenis at most10^5. Using the heights directly as array indices is not feasible. - Implementation:
Sort → deduplicate to map all heights that appear into the compact integer interval[1, size]. A heightHreceives a new rankidxequal to its position in the sorted, deduplicated arrayb, plus 1. Thisidxindexes bothans_cand the Fenwick tree.
- Motivation:
Fenwick tree
- What it does:
A Fenwick tree efficiently supports both point updates and prefix-sum queries, each inO(log n)time. - How this approach uses it:
I use the tree to answer a central question dynamically: “Among the elements I have processed so far, how many have height ranks in a given range?”update(idx, 1): When processing a child of height rankidx, this means “increase the number of people with height rankidxby 1.”query(idx): This returns “the total number of people whose height ranks lie in[1, idx].”
- What it does:
Traversal in both directions
- Reasoning:
An element’s total inversion countkequals the number of greater elements on its left, plus the number of smaller elements on its right. We calculate the two parts separately.
- Reasoning:
Algorithm flow
- a. Forward traversal (inversions with elements on the left):
- Traverse the original height array
afrom left to right. - For each height
p, first query the Fenwick treecto count the elements to the left ofpwhose values are greater thanp. - Assign this count to
ans_c[idx], whereidxis the rank of heightp. - Perform
update(idx, 1)to add the current elementpto the processed set.
- Traverse the original height array
- b. Reverse traversal (inversions with elements on the right):
- Clear the Fenwick tree
c. - Traverse the original array
afrom right to left. - For each height
p, querycto count the elements to its right whose values are smaller thanp. - Add this count to
ans_c[idx]. - Perform
update(idx, 1).
- Clear the Fenwick tree
- c. Compute the total:
- Traverse
ans_c, calculatesum(k)for eachk = ans_c[idx], and add the results to obtain the final answer.
- Traverse
Feasibility Analysis
Before coding, I estimated the complexity:
Time complexity:
- Coordinate compression: The main operation is
std::sort, which takesO(n log n). - Two traversals: These are two independent loops over
nelements. Within each iteration,lower_boundtakesO(log size), and the Fenwick tree’squeryandupdatealso each takeO(log size). The two traversals therefore takeO(n log size)overall. - Final summation:
O(n)orO(size).
- Since
size <= n, the overall time complexity isO(n log n). Forn = 10^5,10^5 * log(10^5)is approximately1.7 * 10^6, well below the safe threshold of10^8, so it will not TLE.
- Coordinate compression: The main operation is
Space complexity:
- The two
vectorcontainersaandbuseO(n)space. - The Fenwick tree
cnt_cand result arrayans_ceach useO(size). In the worst case,size = n, givingO(n).
- The overall space complexity is
O(n), and memory use is acceptable forn = 10^5.
- The two
Conclusion:
From the perspective of time and space complexity, the approach is feasible.
Implementation
I built the first version around this idea. Its central structure is the length-size array ans_c, intended to hold the total inversion count for a child of height rank idx. At the time, I had not noticed a serious flaw in the design. My first submission consequently earned the points for only one test case.
Expand/collapse the initial code
1 |
|
What Went Wrong
The submission earned only 10 points because its data structures did not match the problem’s requirements, creating a serious logical flaw.
Root cause:
When several elements have the same height, coordinate compression maps them to the same rankidx. During the calculation,ans_c[idx]therefore incorrectly overwrites or combines data belonging to elements at different original positions.For example, in
[H1, H2, H1], the calculation for the secondH1directly overwrites the result for the first, losing information.Conclusion:
The counting granularity must correspond to each of thenindividual elements, rather than thesizedistinct attribute categories.
V2.0: Improvement (Accepted Solution)
To correct this flaw, we need to change the data structure so that each original element has its own record.
Design
- Create an array
ans_aof lengthn, whereans_a[i]stores the total number of swaps involving theith element in the original sequence. - Forward traversal (inversions with elements on the left):
for i from 0 to n-1:- Query the Fenwick tree
cfor the number of elements in[0, i-1]whose values are greater thana[i]. - Add the result to
ans_a[i]. - Update
cwith the information fora[i].
- Reverse traversal (inversions with elements on the right):
- Clear the Fenwick tree
c. for i from n-1 down to 0:- Query
cfor the number of elements in[i+1, n-1]whose values are smaller thana[i]. - Add the result to
ans_a[i]. - Update
cwith the information fora[i].
- Clear the Fenwick tree
- Compute the total:
- Traverse
ans_a, calculatesum(k_i)for eachk_i = ans_a[i], and add the results to obtain the final answer.
- Traverse
Feasibility Analysis
Time complexity:
- The central computation is identical to V1.0: “coordinate compression + two loops of
niterations, each containinglog sizeoperations.” The overall time complexity remainsO(n log n), so the algorithm remains efficient.
- The central computation is identical to V1.0: “coordinate compression + two loops of
Space complexity:
- The only difference from V1.0 is the result array. The length-
narrayans_areplaces the length-sizearrayans_c. In the worst case,size=n, so their space requirements are comparable. - The overall space complexity remains
O(n).
- The only difference from V1.0 is the result array. The length-
Conclusion:
From the perspective of time and space complexity, the approach is feasible.
Implementation
By using array index i to identify the original position, this approach gives every piece of data a unique owner. The code is also simpler than V1.0.
Final accepted submission record
Expand/collapse the final accepted code
1 |
|
Implementation detail: symmetry between the forward and reverse traversals
The two traversals in my code show an elegant symmetry in the order and logic of
updateandquery. It is worth examining this in detail.Forward traversal (inversions on the left): “update, then query”
1
2
3
4
5for (int i = 0; i < n; i++) {
int idx = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
update(idx, 1, size, cnt_c);
ans_a[i] += i + 1 - query(idx, cnt_c);
}- Execution order:
Add the current elementa[i]to the Fenwick tree withupdate, then performquery. - Interpretation:
query(idx, cnt_c)counts the elements in[0, i]whose values are less than or equal toa[i], includinga[i]itself. Subtracting this from the processed counti + 1gives the number of values greater thana[i]in[0, i]. Since an element cannot be greater than itself, this is exactly the number of inversions involvinga[i]and an element on its left.
- Execution order:
Reverse traversal (inversions on the right): “query, then update”
1
2
3
4
5for (int i = n - 1; i >= 0; i--) {
int idx = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
ans_a[i] += query(idx - 1, cnt_c);
update(idx, 1, size, cnt_c);
}- Execution order:
Performqueryfirst, then add the current elementa[i]to the Fenwick tree withupdate. - Interpretation:
query(idx - 1, cnt_c)counts the elements already in the tree beforea[i]is added—that is, the elements in[i+1, n-1]—whose ranks are strictly less thanidx, meaning their values are strictly less thana[i]. This directly gives the number of inversions involvinga[i]and an element on its right.
- Execution order:
Comparison:
The forward traversal uses “update, then query” and subtracts from the total to remove the current element’s effect. The reverse traversal uses “query, then update” to obtain the desired count directly. Their execution orders are opposite, yet both compute the correct result, illustrating the flexibility of algorithm implementation.
P.S. Finally, here are my messy scratch notes from working on the problem (
Figure legend: The scratch arrays work through coordinate compression and inversion counts on several examples, including repeated heights. The Chinese labels “front” and “back” identify the contributions from the two traversal directions; “expected” marks the intended counts. The final example sums the individual unhappiness values to 26.
Takeaways
- The value of a good reduction: The key is turning a complicated sorting problem with nonlinear costs into a clean combinatorial counting problem: count the inversions involving each element in the sequence, in separately computable steps.
- Match implementation to the model: Data structures and their implementation must match the model at the individual-element level. This is essential for avoiding logical errors when duplicate elements are present.
- Use the right tool: A Fenwick tree is an efficient tool for dynamic prefix sums, making it well suited to the dynamic range counting required by both traversals here.
I hope this review offers a clear approach to similar problems.