洛谷 P1168 中位数

1 P1168 中位数

2 题目描述

时间限制 \(1s\) | 空间限制 \(125M\)

给出一个长度为 \(N\) 的非负整数序列\(A_i\),对于所有 \(1 ≤ k ≤ (N + 1) / 2\),输出\(A_1, A_1 \sim A_3, …,A_1 \sim A_{2k - 1}\) 的中位数。即前 \(1,3,5,…\) 个数的中位数。

数据范围:\(A_i≤10^9\)

3 题解

显然,长度为 \(n\)\(n\) 为奇数)的序列的中位数是将这个序列排序后这个序列的第 \(\lceil \dfrac{n}{2} \rceil\) 个数。

观察到这道题中所有要查询的区间都是 \([1, r]\),且每次只会往右增加 \(r\),我们可以用一棵权值线段树就解决问题:每次往新的位置 \(modify\) 即可。

接下来,我们只需要在权值线段树上查找全局第 \(k\) 小值就行了。

这里引入权值线段树上二分的做法:对于权值线段树上每一段区间,记录其中的个数。设我们需要查找第 \(pos\) 名,如果在查找过程中发现 \(pos\) 小于左区间的个数,那么我们直接返回左区间中的第 \(pos\) 名。如果 \(pos\) 大于等于左区间的个数,那么我们返回右区间中的第 \(pos - l.sum\) 名即可。

这里发现值域范围是 \(10^9\),普通线段树开不下,我们就采用动态开点线段树存储。

4 代码(空格警告):

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e5+10;
int n, tot, rt;
int a[N];
struct node
{
    int lc, rc, sum;
}t[(int)1e7];
int build()
{
    tot++;
    t[tot].lc = t[tot].rc = t[tot].sum = 0;
    return tot;
}
void pushup(int p) {t[p].sum = t[t[p].lc].sum + t[t[p].rc].sum;}
void modify(int p, int l, int r, int pos)
{
    if (l == r)
    {
        t[p].sum++;
        return ;
    }
    int mid = (l+r)/2;
    if (pos <= mid)
    {
        if (!t[p].lc) t[p].lc = build();
        modify(t[p].lc, l, mid, pos);
    }
    else
    {
        if (!t[p].rc) t[p].rc = build();
        modify(t[p].rc, mid+1, r, pos);
    }
    pushup(p);
}
int query(int p, int l, int r, int pos)
{
    if (l == r) return l;
    int mid = (l+r) / 2;
    if (t[t[p].lc].sum > pos) return query(t[p].lc, l, mid, pos);
    return query(t[p].rc, mid+1, r, pos - t[t[p].lc].sum);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    rt = build();
    modify(rt, 1, 1e9, a[1]);
    printf("%d\n", a[1]);
    for (int i = 2; i < n; i += 2)
    {
        modify(rt, 1, 1e9, a[i]);
        modify(rt, 1, 1e9, a[i+1]);
        printf("%d\n", query(rt, 1, 1e9, i / 2));
    }
    return 0;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-05-03 11:45  David24  阅读(97)  评论(1编辑  收藏  举报