中位数之最

Max Median

题意

给出一个长度为 (n) 的序列 (a),你能任选一个区间长度区不小于 (k) 的区间,那么在这些的区间中能得到的区间中位数最大值是多少?

此题中位数指:有 (n) 个数,第 (lfloor frac{n+1}{2} floor) 个数记为中位数。

数据范围:(1≤k≤n≤2*10^5,1≤a_i≤n)

思路

把所以满足条件的区间中位数都放入一个集合中,集合的最大值即为答案。

通过枚举的方式得到集合肯定是不行的,但可以通过对集合内 $ >x$ 的元素计数的方式来确定一个数在集合中的位置,比如若集合最大值 (leq x),那么集合中 (>x) 的元素个数为 (0) ,否则集合中 (>x) 的元素个数不为 0,显然 (x) 具有单调性,通过计数结果可以对 (x) 的位置进行检验。

计数方法,设 (p_i)([1,i])(leq x) 的数的个数。如果区间 ([l, r]) 的中位数 (>x),可以得到 (r-(l-1) > 2*(p_r-p_{l-1})) ,将式子简化为 (r-2*p_r > (l-1) - 2*p_{l-1}) ,又因为区间长度至少为 (k),所以对于右端点 (r),它的左端点只能在 ([0,r-k]) 之间选定,这就可以用树状数组进行计数。

代码

#include<bits/stdc++.h>

using namespace std;

const int maxn = 2e5+1;
int a[maxn], b[maxn], n, k;
int p[maxn], q[maxn], tree[maxn<<1];

void add(int x, int val) {
    for(int i = x; i <= 2*n+1; i += i&-i) {
        tree[i] += val;
    }
}

int sum(int r) {
    int res = 0;
    for(int i = r; i; i -= i&-i) {
        res += tree[i];
    }
    return res;
}

bool check(int x) { // true 为x小于最值
    long long res = 0;
    memset(tree, 0, sizeof tree);
    q[0] = n+1;
    for(int i = 1; i <= n; ++ i) {
        p[i] = p[i-1] + 2*(a[i]<=x);
        q[i] = i-p[i]+n+1;
        if (i < k) continue;
        add(q[i-k], 1);
        res += sum(q[i]-1);
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> k;
    for(int i = 1; i <= n; ++ i) {
        cin >> a[i];
    }
    int l = 1, r = n;
    while(l < r) {
        int mid = l + r >> 1;
        if (check(mid)) l = mid + 1;
        else r = mid;
    }
    cout << l << "
";
    return 0;
}
原文地址:https://www.cnblogs.com/yycx/p/14417891.html