题目大意
给定长度为 的整数数组
和整数
,考虑所有长度
的连续子数组,对每个子数组取其下中位数(偶数长度取两中间较小者),求这些中位数中的最大值。
下中位数定义:长度为
的序列排序后取第
个元素(1-indexed)
例如:[2,7,3,5]→ 排序[2,3,5,7]→ 中位数 = 第 2 个 =3
解题思路
核心观察
- 答案一定是原数组中的某个元素(因为中位数来自子数组元素)
- 直接枚举所有子数组不可行(
子数组,每组排序
→ 超时)
二分答案 + 判定转化
我们二分答案 ,问题转化为:
是否存在一个长度
的子数组,其中位数
?
如何高效判定?
构造辅助数组 :
关键性质:
子数组的中位数 当且仅当 其对应的
子数组和
。
证明:设子数组长度
,
的元素有
个
中位数⇨
⇨
⇨
前缀和优化判定
用前缀和 delta[i] = b[0]+...+b[i-1],则子数组 v[l..r] 的和为 delta[r+1] - delta[l]。
要判断是否存在长度 的子数组满足和
,只需:
- 遍历右端点
(对应前缀和
delta[i]) - 维护
min_prefix = min(delta[0], ..., delta[i-m]) - 若
delta[i] - min_prefix >= 1,则存在合法子数组
时间复杂度: 每次判定
算法步骤
- 去重排序:将数组元素去重并排序(用于二分候选答案)
- 二分答案:在排序后的数组上二分索引
- 判定函数
check(x):- 构造前缀和数组
delta - 维护最小前缀和
min_prefix - 若存在
delta[i] - min_prefix > 0(即),返回
true
- 构造前缀和数组
- 输出最大可行答案
复杂度分析
-
时间复杂度:
- 每组测试数据:
- 使用
set去重并构建search数组: - 二分答案:最多
次迭代(因为去重后最多
个不同值)
- 每次
check函数: - 小计:
- 使用
- 题目保证所有测试数据中
的总和不超过
- 因此总时间复杂度为:
完全可以通过。
- 每组测试数据:
-
空间复杂度:
,用于存储数组、前缀和及去重后的候选值。
代码实现
#include <bits/stdc++.h>
using namespace std;
bool check(int x, vector<int>& v, int m) {
int n = v.size();
vector<int> delta(n + 1, 0);
// 构造前缀和:>=x 为 +1,否则 -1
for (int i = 0; i < n; i++) {
delta[i + 1] = delta[i] + (v[i] >= x ? 1 : -1);
}
int min_prefix = 0; // delta[0] = 0
// 遍历右端点 i(对应子数组结束于 i-1)
for (int i = 1; i <= n; i++) {
if (i >= m) {
// 检查是否存在左端点 j ∈ [0, i-m] 使得 delta[i]-delta[j] >= 1
if (delta[i] - min_prefix > 0) {
return true;
}
// 更新 min_prefix:加入 delta[i - m + 1](为下一轮准备)
min_prefix = min(min_prefix, delta[i - m + 1]);
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
while (q--) {
int n, m;
cin >> n >> m;
vector<int> v(n);
set<int> st;
for (int i = 0; i < n; i++) {
cin >> v[i];
st.insert(v[i]);
}
// 去重并排序
vector<int> search(st.begin(), st.end());
int l = 0, r = (int)search.size() - 1;
int ans = 0;
// 二分答案(在排序后的数组上)
while (l <= r) {
int mid = (l + r) / 2;
if (check(search[mid], v, m)) {
ans = mid; // 记录可行答案
l = mid + 1; // 尝试更大的值
} else {
r = mid - 1; // 尝试更小的值
}
}
cout << search[ans] << '\n';
}
return 0;
}

京公网安备 11010502036488号