POJ 2104 K-th Number

刚开始接触线段树,然后碰到这道题用“线段树”的方式去思考没想明白,然后看了一篇题解恍然大悟,方法果然高明


题目描述:

给定数组a[n](n < 100,000),做m次查询(m < 5,000),查询内容为[a, b]内第k大的数

如果对每一次查询都先排序的话肯定会超时(mnlogn),既然数组是给定了的,我们可不可以做一些预处理呢?

从有序的数组中找第k大的数是容易的,但是我们显然无法对每一段数组都先进行排序,这样空间是承受不起的

如果查询的区间越大排序所需要的时间越长,如果可以把这个区间缩小的话查找就容易多了

既然如此的话我们可不可以让这个数组大体有序?

通过二分的思想,我们可以很容易的做到让这个数组的前一半比后一半小,可以构造这样一个预处理的二维数组,每一层都是上一层子区间二分后的结果,所需空间为maxLength*log(maxLength)

接下来查询时要做的就是把某一层[a, b]内第k大的数转换为下一层的[a1, b1]找第k1个数,这样每一次搜索的范围都减半,每次查询的时间便是logn了

下面的问题就是如何把[a, b]转换为[a1,  b1],k如何转换为k1了

上面说要将一个区间分成两个,前一半比后一半小,首先要知道的是第k个数应该到哪个区间去找,记中位数为mid,要看的就是第k个数比mid大还是小了,如果查询区间内的数中有t个被塞入了前半个区间(<mid),这t个数是查询区间中的前t小个数,那么如果k在这t个之中,那么显然k < mid,应该到前半个区间去找,所以查找的区间由k和t的大小关系决定

于是t该怎么算?在预处理的时候另开一个数组left[i]记录截止到i为止有多少个点比mid小被扔进了前半个数组就好了,这样[a, b]内的t = left[b] - left[a - 1](注意a = 区间边界的情况)

在向下一个区间查找的转移过程中,应保证区间中元素的相对位置不变,这样才能保证查询区间不会混入其他的元素,这个比较好处理,分区间时顺序的扫一遍再由mid筛选自然就形成了

最后当查询的区间长度变为1的时候,那个值自然就是我们想要的了


下边举一个预处理二维数组的例子

a[n] 1 5 2 6 3 7 4 mid=5

depth=1 1 2 3   | 5 6 7 4 mid=2 | mid = 6

depth=2 1   | 2 3   | 5 4   | 6 7

depth=3 1   | 2  | 3   | 4  | 5   | 6   | 7

不难发现到最后的时候数组就是完全排序的了      




下面是源码,写的比较挫==也没加注释,以后有时间改....

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#define MAXN 100010

int a[21][MAXN],left[21][MAXN];
struct treeNode
{
    int s;
    int e;
    int mid;
    int depth;
}tree[MAXN * 3];

int cmp(const void * a, const void * b)
{
    return *(const int *)a - *(const int *)b;
}
void buildTree(int s, int e, int depth, int tr)
{
    tree[tr].s = s;
    tree[tr].e = e;
    tree[tr].depth = depth;
    tree[tr].mid = (s + e) >> 1;
    if (s >= e - 1) return;

    int mid = (e + s) >> 1;
    int t = a[20][mid];
    int i = s, j = mid;
    for (int k = s; k < e; k++)
    {
        if (a[depth][k] < t)
            a[depth + 1][i++] = a[depth][k];
        else
            a[depth + 1][j++] = a[depth][k];
        left[depth][k] = i - s;
    }
    buildTree(s, mid, depth + 1, 2 * tr + 1);
    buildTree(mid, e, depth + 1, 2 * tr + 2);
    return;
}

int query(int s, int e, int k, int pTree)
{
    treeNode & tr = tree[pTree];
    if (s == e - 1) return a[tr.depth][s];
    if (s == tr.s && e == tr.e) return a[20][tr.s + k - 1];
    int f = (s == tr.s ? 0 : left[tr.depth][s - 1]);
    int t = left[tr.depth][e - 1] - f;
    if (k <= t)
        return query(tr.s + (s == tr.s ? 0 : left[tr.depth][s - 1]), tr.s + left[tr.depth][e - 1], k, pTree * 2 + 1);
    else
        return query(tr.mid + (s - tr.s - f), tr.mid + (s - tr.s - f) + e - s - t, k - t, pTree * 2 + 2);
}

int main()
{
    int n, m, s, e ,k;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
    {
        scanf("%d", &a[0][i]);
    }
    memcpy(a[20], a[0], sizeof(int) * n); // 用a[20]来记录完全排序的结果
    qsort(a[20], n, sizeof(int), cmp);
    buildTree(0, n, 0, 0);

    for (int i = 0; i < m; ++i)
    {
        scanf("%d%d%d", &s, &e, &k);
        printf("%d\n", query(s - 1, e, k, 0));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值