题目要求:给定一个长度为 ( n ) 的数组 ( a ),对所有区间 ([l, r])((1 \le l \le r \le n)),求出每个区间第 ( K ) 大的数(若区间长度 < K,则忽略该区间),然后将所有这些“第 K 大”收集起来,再求这整个集合中的第 ( M ) 大的数。
核心思路:二分答案 + 双指针/尺取法计数
我们无法枚举所有区间,但可以二分答案:
设最终答案为 ( X ),若我们能快速判断“至少有多少个区间的第 K 大 ≥ X”,即可判断 ( X ) 是否 ≥ 真实答案。
一个区间的第 K 大 ≥ X
⇔ 该区间中 ≥ X 的数 ≥ K 个
(因为将区间内数降序排列后,第 K 个 ≥ X 当且仅当前 K 个都 ≥ X,等价于总共有至少 K 个 ≥ X 的数)
因此,对一个给定的 ( X ),问题转化为:
有多少个区间 ([l, r]) 满足:区间内 ≥ X 的元素个数 ≥ K?
此问题可用双指针(滑动窗口)在 ( O(n) ) 时间内解决。
算法步骤
Step 1::定义 check(X)
典型双指针问题:
long long count_GE_K(const vector<int>& a, int K, int X) {
int n = a.size();
long long cnt = 0;
int left = 0;
int ones = 0;
for (int right = 0; right < n; ++right) {
if (a[right] >= X) ones++;
while (ones >= K) {
// [left, right] 满足 ones >= K,则 [left, right], [left+1, right], ..., [right, right] 都满足
cnt += (n - right); // 固定 left,右端点 ≥ right 的都合法(因为扩展右端点不会减少 ones)
if (a[left] >= X) ones--;
left++;
}
}
return cnt;
}
注意:上面逻辑是常见错误点!
正确做法是:对于每个 right,找到最小的 left 使得 [left, right] 中 ≥X 的数 ≥ K,那么所有 l ∈ [0, left_min] 对应的 [l, right] 都满足条件(因为缩短左端点只会增加/不变元素个数)。
正确双指针写法:
long long count_GE_K(const vector<int>& a, int K, int X) {
int n = a.size();
long long cnt = 0;
int left = 0;
int ones = 0;
for (int right = 0; right < n; ++right) {
ones += (a[right] >= X);
// 缩小窗口直到 ones < K(即 [left, right] 是最后一个满足 ones >= K 的起点)
while (ones >= K) {
ones -= (a[left] >= X);
left++;
}
// 此时 [0, left-1] 开头、以 right 结尾的区间都满足 ones >= K
cnt += left; // l = 0,1,...,left-1 共 left 个
}
return cnt;
}
举例验证:
a = [3,1,4,2], K=2, X=2 → b = [1,0,1,1]
right=0: ones=1 <2 → cnt+=0, left=0
right=1: ones=1 <2 → cnt+=0
right=2: ones=2 ≥2 → enter while: subtract a[0]=1 → ones=1, left=1 → exit
cnt += left = 1 (即 [1,2]: [1,4] 满足?b=[0,1,1] sum=2 ✓;[0,2]: [3,1,4] 也满足但 left 已移出?)
→ 错误!
问题出在:当 right=2 时,[0,2] 和 [1,2] 都满足,但上面只加了 left=1,漏了 [0,2]。
正确逻辑应是:维护窗口 [l, r] 中 ones ≥ K 的最小 l(即 l_min),则所有 l ≤ l_min 的起点都合法。
更稳妥写法:固定 r,求最大 l_max 使得 [l_max, r] 中 ones ≥ K,则合法 l 为 0 ~ l_max,共 l_max + 1 个。
但用双指针找 l_max 不方便(要扩展左端点),所以我们换种方式:
维护一个窗口 [l, r] 使得其中 ones = K-1,则 l 是第一个不满足的位置,l-1 是最后一个满足的位置。
经典模板(计数 ≥K 的区间数):
long long count_GE_K(const vector<int>& a, int K, int X) {
if (K == 0) return 1LL * a.size() * (a.size() + 1) / 2;
int n = a.size();
long long cnt = 0;
int l = 0;
int sum = 0;
for (int r = 0; r < n; ++r) {
sum += (a[r] >= X);
// 缩小 l 使得 [l, r] 中 sum == K-1(即刚好不够)
while (sum >= K) {
sum -= (a[l] >= X);
++l;
}
// 此时 [0, l-1] 作为起点,[start, r] 都满足 sum >= K
cnt += l; // start = 0,1,...,l-1
}
return cnt;
}
再验证:
a = [3,1,4,2], K=2, X=2 → b=[1,0,1,1]
r=0: sum=1 <2 → l=0, cnt+=0
r=1: sum=1 <2 → cnt+=0
r=2: sum=2 ≥2 → enter while: sum-=a[0]>=2?1 → sum=1, l=1 → exit
cnt += l = 1 (start=0: [0,2] 满足 ✓)
r=3: sum += a[3]>=2?1 → sum=2 ≥2
while: sum-=a[1]>=2?0 → sum=2, l=2; still ≥2
sum-=a[2]>=2?1 → sum=1, l=3 → exit
cnt += l = 3 (start=0:[0,3], start=1:[1,3], start=2:[2,3] — 共3个)
总 cnt=1+3=4
枚举所有 len≥2 区间(共 3+2+1=6 个):
[0,1]: [3,1] → 第2大=1 <2 → 不计
[0,2]: [3,1,4] → 排:4,3,1 → 第2大=3 ≥2 ✓
[0,3]: [3,1,4,2] → 4,3,2,1 → 第2大=3 ≥2 ✓
[1,2]: [1,4] → 4,1 → 第2大=1 <2 ✗
[1,3]: [1,4,2] → 4,2,1 → 第2大=2 ≥2 ✓
[2,3]: [4,2] → 4,2 → 第2大=2 ≥2 ✓
→ 共4个 ✓
Step 2:二分答案
- 设 ( f(X) = ) 有多少个区间的第 K 大 ≥ X
- ( f(X) ) 是非递增函数:X↑ ⇒ 满足条件的区间数↓
- 要找最大的 ( X ) 使得 ( f(X) \ge M )
(因为 ( S ) 中第 ( M ) 大 = 满足 ( \ge X ) 的数 ≥ M 的最大 X)
标准二分:
int low = 1, high = 1e9;
while (low < high) {
int mid = (low + high + 1) / 2; // 上取整,避免死循环
if (count_GE_K(a, K, mid) >= M) {
low = mid;
} else {
high = mid - 1;
}
}
answer = low;
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll count_GE_K(const vector<int>& a, int K, int X) {
int n = a.size();
if (K == 0) return 1LL * n * (n + 1) / 2;
ll cnt = 0;
int l = 0;
int sum = 0;
for (int r = 0; r < n; ++r) {
sum += (a[r] >= X);
while (sum >= K) {
sum -= (a[l] >= X);
++l;
}
cnt += l; // start in [0, l-1]
}
return cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, K;
ll M;
while (cin >> n >> K >> M) {
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
int low = 1, high = 1e9;
while (low < high) {
int mid = (low + high + 1) >> 1;
if (count_GE_K(a, K, mid) >= M) {
low = mid;
} else {
high = mid - 1;
}
}
cout << low << '\n';
}
return 0;
}
复杂度分析
- 时间:二分 ( O(\log \max a_i) \approx 30 ) 次,每次
count_GE_K为 ( O(n) ) → 总 ( O(n \log 10^9) ) - 空间:( O(n) )
满足 ( n \le 10^5 ) 要求(HDU 实测 300~500 ms)
总结
- 第 K 大 ≥ X ⇔ 区间中 ≥ X 的数 ≥ K
- 二分答案 + 双指针计数是解决“第 M 大的第 K 大”类问题的标准范式
- 双指针写法务必验证边界(尤其是 K=1, K=n 极端情况)

京公网安备 11010502036488号