Below is a complete, line-by-line walk-through of the provided solution, with an explanation of the logic, the algorithmic ideas, the complexity, and a discussion of potential pitfalls or edge-cases.
Feel free to skip to the sections that interest you most (e.g. “Complexity”, “Possible Bugs”, etc.) – the full analysis is deliberately long because you asked for a very thorough review.
—
1. Problem Recap (in my own words)
We have a sequence of n integers a[1...n].
A subarray is a contiguous segment a[l...r].
We call a subarray good if
`
sum(a[l...r]) > k * len(a[l...r]) (len = r-l+1)
The task: for each test case, output the number of good subarrays.
Constraints (from the statement):
1 ≤ t ≤ 10^4
1 ≤ n ≤ 2·10^5 (sum of n over all test cases ≤ 2·10^5)
1 ≤ k ≤ 10^9
1 ≤ a[i] ≤ 10^9
So we need an algorithm that runs in roughly O(n log n) or better per test case.
---
2. High-level Idea
Transform the inequality into a prefix-sum comparison:
sum(l...r) > k * len(l...r)
⇔ (a[l] + ... + a[r]) - k*(r-l+1) > 0
⇔ (a[l]-k) + ... + (a[r]-k) > 0
Let b[i] = a[i] – k.
Define prefix sums of b:
pref[0] = 0
pref[i] = b[1] + ... + b[i] (for i ≥ 1)
Now
sum(l...r) > k*len(l...r) ⇔ pref[r] - pref[l-1] > 0
⇔ pref[l-1] < pref[r]
So the number of good subarrays is exactly the number of pairs (i, j) with 0 ≤ i < j ≤ n such that pref[i] < pref[j].
That is a classic “count inversions” problem on the array pref[0…n], but instead of counting > we count <.
We can solve it with a divide-and-conquer (merge-sort) algorithm that counts cross-pairs while merging.
The algorithm runs in O(n log n) and needs only linear extra memory.
---
3. Detailed Code Walk-through
Below is the entire code, with comments inserted by me for clarity.
python
import sys
def solve() -> None:
data = sys.stdin.buffer.read().split()
t = int(data[0])
idx = 1
out_lines = []
for _ in range(t):
n = int(data[idx]); idx += 1
k = int(data[idx]); idx += 1
a = list(map(int, data[idx: idx + n])); idx += n
pref = [0] * (n + 1)
for i in range(1, n + 1):
pref[i] = pref[i-1] + a[i-1] - k
# Merge-sort counting
def merge_sort_count(arr):
m = len(arr)
if m <= 1:
return arr, 0
mid = m // 2
left, inv_left = merge_sort_count(arr[:mid])
right, inv_right = merge_sort_count(arr[mid:])
inv = inv_left + inv_right
merged = []
i = j = 0
# Count cross inversions: left[i] < right[j]
while i < len(left) and j < len(right):
if left[i] < right[j]:
merged.append(left[i])
i += 1
inv += len(right) - j
else:
merged.append(right[j])
j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged, inv
_, ans = merge_sort_count(pref)
out_lines.append(str(ans))
sys.stdout.write("
".join(out_lines))
if __name__ == "__main__":
solve()
Below is a line-by-line explanation of the most important parts.
| Line | Code | Explanation |
|---|---|---|
import sys |
Import the standard I/O library. | Needed for fast input (sys.stdin.buffer.read()). |
def solve() -> None: |
Define the main routine. | Encapsulates the solution logic. |
data = sys.stdin.buffer.read().split() |
Read all input as bytes and split on whitespace. | Fastest way to read many integers in Python. |
t = int(data[0]) |
Number of test cases. | First token. |
idx = 1 |
Pointer into data. |
Keeps track of which token we're at. |
for _ in range(t): |
Loop over test cases. | Outer loop. |
n = int(data[idx]); idx += 1 |
Read n. |
Number of elements in this test case. |
k = int(data[idx]); idx += 1 |
Read k. |
Threshold multiplier. |
a = list(map(int, data[idx: idx + n])); idx += n |
Read the n array elements. |
a holds the original array. |
pref = [0] * (n + 1) |
Allocate prefix sum array. | pref[0] = 0 by definition. |
for i in range(1, n + 1): |
Compute prefix sums of b[i] = a[i-1] – k. |
Loop from 1 to n inclusive. |
pref[i] = pref[i-1] + a[i-1] – k |
Update prefix. | a[i-1] because a is 0-indexed. |
def merge_sort_count(arr): |
Recursive function to sort and count inversions. | Classic merge-sort inversion counter. |
m = len(arr) |
Length of current sub-array. | Base case when m <= 1. |
if m <= 1: return arr, 0 |
Return unchanged array and zero inversions. | Trivial case. |
mid = m // 2 |
Split point. | Integer division. |
left, inv_left = merge_sort_count(arr[:mid]) |
Recurse on left half. | Returns sorted left part and its inversions. |
right, inv_right = merge_sort_count(arr[mid:]) |
Recurse on right half. | Same for right part. |
inv = inv_left + inv_right |
Sum of internal inversions. | Inversions that lie entirely within left or right. |
merged = [] |
Temporary array for merge. | Will hold the sorted result. |
i = j = 0 |
Pointers into left and right. |
Standard merge-sort merge. |
while i < len(left) and j < len(right): |
Main merge loop. | Keep merging until one side is exhausted. |
if left[i] < right[j]: |
Condition for a cross-inversion. | Because we count pairs with pref[l] < pref[r]. |
merged.append(left[i]) |
Append the smaller element. | Keep array sorted. |
i += 1 |
Move left pointer. | |
inv += len(right) – j |
Count inversions contributed by left[i]. |
All remaining elements in right are larger. |
else: |
The other side is smaller. | No new inversions. |
merged.append(right[j]) |
Append element from right. | |
j += 1 |
Move right pointer. | |
merged.extend(left[i:]) |
Append any remaining left elements. | |
merged.extend(right[j:]) |
Append any remaining right elements. | |
return merged, inv |
Return sorted array and inversion count. | |
_, ans = merge_sort_count(pref) |
Call the function on the prefix array. | ans is the desired number of good subarrays. |
out_lines.append(str(ans)) |
Store answer for this test case. | |
| sys.stdout.write(” “.join(out_lines)) |
Output all answers at once. | Efficient printing. |
---
4. Complexity Analysis
| Step | Operation | Complexity |
|---|---|---|
| Reading input | O(total_n) |
Total number of integers read. |
Building pref |
O(n) |
Single pass. |
| Merge-sort counting | O(n log n) |
Standard merge-sort with inversion counting. |
| Writing output | O(t) |
Number of test cases. |
Overall per test case: O(n log n) time, O(n) memory.
With Σ n ≤ 2·10^5, this easily fits within the limits.
---
5. Correctness Proof Sketch
We need to show that the algorithm outputs exactly the number of good subarrays.
- Transformation
For any subarray a[l…r], let b[i] = a[i] – k.
Then
sum(a[l...r]) > k * len(a[l...r])
⇔ sum(b[l...r]) > 0
⇔ pref[r] - pref[l-1] > 0
⇔ pref[l-1] < pref[r]
So each good subarray corresponds to a pair (i, j) with 0 ≤ i < j ≤ n and pref[i] < pref[j].
The mapping is bijective: each such pair defines a unique subarray a[i+1…j].
- Counting Pairs
The merge-sort routine counts exactly the number of pairs (i, j) with i < j and pref[i] < pref[j].
This is the standard inversion counting algorithm, but with the comparison reversed (< instead of >).
The algorithm is well-known to be correct:
- It counts inversions inside the left half and right half recursively.
- During the merge step, when an element left[i]
is placed beforeright[j], all remaining elements inrightare greater, solen(right) – jnew pairs(left[i], right[·])are counted. - All pairs are counted exactly once because the recursion partitions the indices into disjoint halves.
- Conclusion
Since the algorithm counts exactly the number of pairs (i, j) with pref[i] < pref[j], it outputs exactly the number of good subarrays.
Therefore the algorithm is correct.
—
6. Edge-case & Pitfall Discussion

| Scenario | Why it matters | How the code handles it |
|---|---|---|
k is very large (e.g., 10^9) |
b[i] = a[i] – k can be negative, but still fits in Python int (unbounded). |
No overflow issue in Python. |
All a[i] equal to k |
Every subarray has sum = k * len, so no subarray is good. |
pref becomes all zeros, so pref[i] < pref[j] never holds → answer 0. |
All a[i] > k |
Every subarray is good. | pref strictly increasing, so every pair (i, j) with i < j counts → answer n*(n+1)/2. |
Large n (up to 2·10^5) |
Must avoid recursion depth overflow. | Recursion depth is log2(n) (~18 for 2·10^5), far below Python's limit (~1000). |
Very many test cases (t up to 10^4) |
Total n is bounded, so overall time is fine. |
Input is read all at once; output is buffered. |
| Negative prefix values | Merge-sort handles negative numbers just fine. | No special handling needed. |
| Duplicate prefix values | We count only <, not ≤. |
The comparison left[i] < right[j] ensures duplicates are not counted as good pairs. |
| Overflow in intermediate sums | pref[i] can be as large as n * (10^9 – 1) ~ 2·10^14, which fits in 64-bit signed int. |
Python ints are arbitrary precision, so no overflow. |
---
7. Possible Optimizations / Alternatives
- Fenwick Tree / Coordinate Compression
Instead of merge-sort, one could compress the prefix values and use a Fenwick tree to count how many smaller prefixes have appeared so far.
Complexity: O(n log n) as well, but with a larger constant (log factor from the tree operations).
The merge-sort method is typically faster in practice due to better cache locality.
- In-place Merge-Sort
The current implementation creates new slices (arr[:mid], arr[mid:]) at each recursion level, which uses extra memory and time.
A more memory-efficient version would pass indices and reuse a single auxiliary array.
However, given the constraints, the current approach is perfectly fine.
- Iterative Merge-Sort
Avoid recursion entirely by performing an iterative bottom-up merge-sort.
This eliminates recursion overhead and can be slightly faster.
- Parallelization
Not needed here; the problem size is small enough for a single thread.
---
8. Summary
- The solution transforms the problem into counting pairs of prefix sums with a <
relation. - A divide-and-conquer (merge-sort) algorithm counts those pairs in O(n log n)
time. - The implementation is straightforward, uses only linear extra memory, and handles all edge cases correctly.
- Complexity fits comfortably within the given constraints (Σ n ≤ 2·10^5`).
Feel free to ask for clarification on any specific part of the code or the reasoning!

