poj2104 (线段树求区间第k大)

本文介绍了一种利用线段树解决区间查询问题的方法,即求解区间内的第K大元素。通过将线段树节点对应的区间变为有序状态,并使用二分查找辅助定位目标值,有效解决了传统暴力解法效率低下的问题。

题目链接:

题意:

给n个数, 每次询问一个区间, 让你输出区间中的第k大的数。

做了这道题,我对线段树的能解决的问题得认识又有提升。 这个是让求区间内的第k大, 询问比较多,暴力显然不行。 我们想用线段树解题,那么线段树应该保存什么呢? 一开始更本没想到, 看了别人的博客明白了, 原来是把线段树上的每一个区间都变成有序的, 并把这个排列的顺序存在另一个数组里, 我们只在线段树中存下这一段序列的起点和终点;之后我们就可以二分查找了。  最后的结果也是我们二分得出, 怎么得出的呢? 方法是: 线段树中每个区间都是有序的, 我们就可以用二分枚举数去查找在这个区间内比当前枚举的数小或相等的个数是多少, 我们只需要找到满足个数为K的那个最小的数;这个数就是我们要找到答案。 

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int maxn = 100010;
const int inf = 1e9;
struct Node
{
    int l, r;
}tree[maxn<<2];
int d[maxn*100];
int top, a;
void build(int l, int r, int rt)
{
    if(l == r)
    {
        scanf("%d", &a);
        d[++top] = a;
        tree[rt].l = top;
        tree[rt].r = top;
        return ;
    }
    int m = (l+r)>>1;
    build(lson);
    build(rson);
    int ll = tree[rt<<1].l, lr = tree[rt<<1].r;
    int rl = tree[rt<<1|1].l, rr = tree[rt<<1|1].r;
    tree[rt].l = top+1;
    //下面排序是归并排序的思想
    while(ll<=lr && rl<=rr)
    {
        if(d[ll] <= d[rl])
            d[++top] = d[ll++];
        else
            d[++top] = d[rl++];
    }
    while(ll <= lr)
        d[++top] = d[ll++];
    while(rl <= rr)
        d[++top] = d[rl++];
    tree[rt].r = top;
}
//二分查找满足的个数
int search(int l, int r, int k)
{
    if(d[r] <= k)
        return r-l+1;
    if(d[l] > k)
        return 0;
    int m, ll = l, rr = r;
    while(ll < rr)
    {
        m = (ll+rr)>>1;
        if(d[m] > k)
            rr = m;
        else
            ll = m+1;
    }
    return ll-l;
}
//找到我们要询问区间 如果总共有4个数, 查询1~3, 我们就查询1~2 和 3~3,这是要分开查的
int query(int L, int R, int k, int l, int r, int rt)
{
    if(L<=l && r<=R)
        return search(tree[rt].l, tree[rt].r, k);
    int sum = 0;
    int m = (l+r)>>1;
    if(L <= m)
        sum += query(L, R, k, lson);
    if(R > m)
        sum += query(L, R, k, rson);
    return sum;
}
int main()
{
    int n, m, sl, sr, x, y, k;
    while(~scanf("%d%d", &n, &m))
    {
        top = 0;
        build(1, n, 1);
        while(m--)
        {
            scanf("%d%d%d", &x, &y, &k);
            sl = -inf, sr = inf;
            int sm, res;
            while(sl < sr)
            {
                sm = (sl+sr)>>1;
                res = query(x, y, sm, 1, n, 1);
                if(res >= k)
                    sr = sm;
                else
                    sl = sm + 1;
            }
            printf("%d\n", sl);
        }
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值