Codeforces Round #703 (Div. 2) D. Max Median【前缀和 | 二分答案】

该博客讨论了一种在给定数组中找到长度至少为k的子数组,使得子数组的中位数最大的问题。通过二分查找优化算法,可以在O(n)时间内解决。文章还探讨了将中位数替换为算术平均值时的解决方案,并提供了AC代码作为示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

  • 给定长度为 n 的数组 a,找到一个长度至少为 k 的子数组 a[l…r],组成这个子数组的元素的中位数最大。中位数为这个子数组排序后的第 ⌊ n + 1 2 ⌋ \lfloor \frac{n+1}{2} \rfloor 2n+1 个数字。
  • 1 ≤ a i ≤ n , 1 ≤ k ≤ n ≤ 2 ⋅ 1 0 5 1 \leq a_i \leq n, 1 \leq k \leq n \leq 2 \cdot 10^5 1ain1kn2105

思路:

  • 对于本题的中位数,如果子数组长度为偶数,中位数取中间靠左那个,因此,子数组中大于等于中位数的元素的一定占所有元素的一半以上,即大于等于中位数的元素个数比小于中位数的多,利用这一点,我们可以在 O ( n ) \mathcal{O}(n) O(n) 的时间判断出一个数 x 是否小于等于中位数,若 x 小于等于中位数,则大于等于 x 的数多于小于 x 的数。由上述的判断过程来看,x 的值是有单调性的,即 x 越大,上述 “大于等于 x 的数多于小于 x 的数” 这个条件越不好满足,如果一个数字满足这个条件了,那么比它小的也一定满足,所以当一个数字满足,我们尝试增大这个数字看它是不是也满足,这样 x 就存在一个最大值,因此可以进行二分答案,直到 x 为最大的那个值。
  • 为了实现上述操作,check时我们可以令大于等于 x 的数为 1,小于 x 的数为 -1,这样如果一个区间的和大于 0,就表示满足 “大于等于 x 的数多于小于 x 的数” 这个条件。我们可以计算一个前缀和,并维护 0 , 1 , . . . , i − k 0, 1, ..., i - k 0,1,...,ik 位置的最小前缀和 mn,如果当前位置 i 的前缀和 pre[i] 满足 pre[i] - mn > 0,就表示有一段长度大于等于 k 的子数组满足条件。

拓展:

  • 若将题目中的中位数换成算数平均值,如何做。
  • check 答案的时候,将序列每个数同时减去答案 x。然后找到一个子数组区间和大于等于 0 的即合法。
  • 因为将每个数同时减去答案 x 后,算数平均值 ≥ 0 即原序列算数平均值 ≥ x。

AC Codes:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, k;
vector<int> a;
bool check(int x) {
    ll mn = 0;
    vector<ll> pre(n + 1);
    for (int i = 1; i <= n; i++)
        pre[i] = pre[i - 1] + (a[i] >= x ? 1 : -1);
    for (int i = k; i <= n; i++) {
        mn = min(mn, pre[i - k]);
        if (pre[i] - mn > 0)
            return true;
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> k;
    a = vector<int>(n + 1);
    for (int i = 1; i <= n; i++) 
        cin >> a[i];
    int l = 1, r = 2e5;
    while (l + 1 < r) {
        int mid = (l + r) >> 1;
        if (check(mid)) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    cout << (check(r) ? r : l) << '\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值