Introduction
This problem was also shared by that skilled programmer from BUAA.
Not long ago, I used a Fenwick tree to solve Luogu’s standard inversion-counting problem. So when I first saw k-multiple inversion pairs, my immediate reaction was: this has to be a variation on the template, and the core tool will still be that familiar Fenwick tree. But getting from the template code to an accepted solution was no simple copy-and-paste job. It was a journey of thoughtful hacking and adapting. In this post, I want to review the whole process: how I started with the inversion-counting template, identified the differences step by step, adjusted the key logic, and ultimately adapted the template into a solution to this problem.
The Problem
Description
Given a sequence a₁, a₂, …, aₙ and a positive integer k, call (i, j) a k-multiple inversion pair if 1 ≤ i < j ≤ n and aᵢ > k · aⱼ. Calculate the number of k-multiple inversion pairs in the sequence.
Input
The first line contains two positive integers n, k (1 ≤ n ≤ 10⁵, 1 ≤ k ≤ 10).
The second line contains n positive integers a₁, a₂, …, aₙ (1 ≤ aᵢ < 2³¹).
To provide more differentiation in scoring, test cases worth 10% of the score guarantee 1 ≤ n ≤ 100.
Output
Print one nonnegative integer: the number of k-multiple inversion pairs in the sequence.
Sample Input
1 | 5 2 |
Sample Output
1 | 4 |
Analysis
The obvious starting point is that classic inversion-counting template problem. It asks us to count pairs with i < j and a[i] > a[j]. My accepted approach at the time was:
- Traverse the array from left to right, considering each
a[j]. - Use a Fenwick tree to query how many numbers that have already appeared before it are greater than
a[j]. - Add up these query results to get the answer.
- After querying, update the Fenwick tree with
a[j]itself.
Here is the code that was accepted then:
Expand/collapse the accepted inversion-counting template code
1 |
|
The framework is very clear. Now, for k-multiple inversion pairs, the only difference is that the condition changes from a[i] > a[j] to a[i] > k * a[j].
That means my changes must center on this difference. When I reach a[j], I no longer need to query for numbers greater than a[j], but for numbers greater than k * a[j].
This seemingly tiny change directly affects the algorithm’s foundation: coordinate compression. In the original template, we only needed to compress the values in array a. Now, to query information involving k * a[j], we also need to include every k * a[j] that could serve as a query boundary in the compression set.
This is the crucial step from template to accepted solution: recognize the change in the query target, and expand the compression set accordingly.
Algorithm Design and Implementation
My entire solution process can be seen as a targeted upgrade of the inversion-counting template.
The Core Idea: Evolving the Template
Keep the template’s framework:
- The overall algorithm follows the inversion-counting template in full: traverse the array → query the contribution → update the data → accumulate the result.
- The core data structure remains a Fenwick tree, because it efficiently supports the point updates and prefix-sum queries we need.
- Since the range of
a[i]is still large, coordinate compression must also remain as a preprocessing step.
Locate the core change: the query target
- The template’s query logic is
贡献 = 总数 - 小于等于a[j]的个数: contribution = total count − count of values less than or equal toa[j]. - For this problem, it becomes
贡献 = 总数 - 小于等于k*a[j]的个数: contribution = total count − count of values less than or equal tok*a[j]. - This
k*a[j]is the new element that the adaptation needs to handle.
- The template’s query logic is
Expand coordinate compression:
- Template: The compression set contains only
{ a[1], a[2], ..., a[n] }. - This problem: The compression set must expand to include both
{ a[1], ..., a[n] }and{ k*a[1], ..., k*a[n] }. Fenwick-tree indices are based on the compressed ranks: we need both to update the rank corresponding toa[j]and to query the rank corresponding tok*a[j]. Compressing them all in one set establishes a common scale.
- Template: The compression set contains only
Adjust the main logic
- For the current element
p(that is,a[j]) in the main loop:- First, find
p‘s compressed rank,idx. - Then find the compressed rank of
k*p,idx2. - Calculate the contribution:
ans += cnt - Sum(idx2, c). Here,cntis the number of processed elements, andSum(idx2, c)is the Fenwick-tree query for the number of previously encountered elements whose values are less than or equal tok*p. - Finally, perform exactly the same update as in the template:
Update(idx, 1, size, c), recording an occurrence ofpin the Fenwick tree.
- First, find
- For the current element
Feasibility Analysis
This “hack” does not change the fundamental structure of the algorithm.
Time complexity:
- The compression set grows to at most
2nelements. Sorting still takesO(n log n). - In the main loop, Fenwick-tree operations and
lower_boundstill takeO(log n). - The overall time complexity remains
O(n log n), comfortably sufficient for the problem.
- The compression set grows to at most
Space complexity:
- Auxiliary array
band Fenwick treecbecome roughly twice as large, but space complexity is stillO(n).
- Auxiliary array
Conclusion:
By identifying the exact difference and expanding the crucial coordinate-compression step, we successfully adapt the inversion-counting template to the new problem. The approach is efficient and feasible.
Implementation
The final accepted code clearly retains the skeleton of the inversion-counting template, with a visible expansion in the compression step to accommodate the new rule.
Expand/collapse the final accepted code
1 |
|
Comparing the Code
The whole adaptation really involves just two key parts.
1. Expanding the Compression Set
Template code: Collect only the values in the original array.
1 | // 离散化集合 b 只需包含 a 内的值 |
Code for this problem: Collect both the original values and their k-fold values.
1 | // 离散化集合 b 必须同时包含 a 内的值及其 k 倍 |
Why: The Fenwick tree needs to query the rank of k*p, so all possible k*p values must be included in the compression set from the start to establish a unified ranking system.
2. Changing the Query Boundary
Template code: Query the number of elements greater than p.
1 | for(int p : a){ |
Code for this problem: Query the number of elements greater than k*p.
1 | for(ll p : a){ |
Why: The core condition changes from > p to > k*p. So when calculating the contribution, the argument passed to Sum must be the rank corresponding to k*p, rather than the rank of p.
P.S. As usual, here are my handwritten working notes at the end.
Diagram guide: The notes sketch Fenwick-tree prefix sums at the top, then work through the n=5, k=2 example and the combined compression set below. The handwritten “含” means “inclusive.”
Takeaways
- Recognize and apply a model: Connect a new problem to a known algorithmic model—the inversion-counting template here—and use it to build a solution framework quickly.
- Analyze the core difference: Identify precisely what makes the variant different. Here, the key change is from
a[i] > a[j]toa[i] > k * a[j]. - Adjust the affected modules: Modify the parts of the algorithm affected by that difference. In this problem, the changed query condition directly requires a corresponding expansion of the coordinate-compression dataset.
- Understand function, not just form: Understanding the purpose of each part of an algorithm is the key to solving problems. It lets us adapt a template effectively as the problem changes, instead of forcing it onto a problem where it does not fit.