POJ 2104 K-th Number 初涉划分树

题意: 给一个数组,对其进行多次查询。每次查询为(L,R,K),即在闭区间内[L,R]第K大的数。

思路:本来没什么思路的,然后学长说这是划分树的模板题,然后就没有然后了......

说一下自己对划分树的理解:


个人感觉划分树就是线段树的一种,每一个节点对应一段区间,节点内有两个数组num[],ans[]。

num[]为对应线段内的所有数据,按输入数据存放。

ans[]内存放的信息为对应线段的[ L, i ]内有多少个数被放到了其左子树中, L 为对应线段的最左边。

对于一段区间,首先找出该区间内的中位数,小于该数的放到其左子树中,大于该数的要放到右子树中。

若存在与中位数相同的数,则要按左右子树中数的个数来讨论,最终要保证左右子树数据量之差不超过 1。

中位数要放到那则可根据自己写线段树时的习惯而定。

查询过程:

对于给定的(L,R,K),判断[L,R]中有多少个数被放到了左子树中,总而得出第K个数被放到了左子树还

是右子树,总而确定下一次递归查询的范围。

每一次递归都要改变(L,R,K),直到L == R。

当 L == R时,节点内存住的数即为答案,递归查询结束。


其实,整个划分树的建立即为一个可以看作一次快速排序的过程,线段树的叶子节点从左到右即为按照一定规则排完序之后的序列。

下图中每一条线段托住的即为线段树中一个节点内的信息,上面为 对应的节点内ans[] 的信息。


#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cstring>

using namespace std;

int num[101000],s[101000];

int stn[25][101000],ans[25][101000];

struct N
{
    int l,r;
} st[401000];

bool cmp(int a,int b)
{
    return a < b;
}

void Init_St(int site,int l,int r,int h)
{
    if(l == r)
    {
        st[site].l = l,st[site].r = r;
        stn[h][l] = s[l];
        return;
    }

    st[site].l = l,st[site].r = r;

    int mid = (l+r)>>1 , i;

    int t1,t2;

    for(i = st[site].l,t1 = st[site].l,t2 = mid+1; i <= st[site].r ; ++i)
    {
        if(i == st[site].l)
        {
            if(stn[h][i] <= s[mid])
            {
                ans[h][i] = 1;
                stn[h+1][t1++] = stn[h][i];
            }
            else
            {
                ans[h][i] = 0;
                stn[h+1][t2++] = stn[h][i];
            }
        }
        else
        {
            if(stn[h][i] <= s[mid])
            {
                ans[h][i] = ans[h][i-1]+1;
                stn[h+1][t1++] = stn[h][i];
            }
            else
            {
                ans[h][i] = ans[h][i-1];
                stn[h+1][t2++] = stn[h][i];
            }
        }
    }

    Init_St(site<<1,l,mid,h+1);
    Init_St(site<<1|1,mid+1,r,h+1);
}

int query(int site,int l,int r,int k,int h)
{
    if(l == r)
    {
        return stn[h][l];
    }

    int mid = (st[site].l + st[site].r)>>1;

    if(k <= ans[h][r] - (l != st[site].l ? ans[h][l-1] : 0))
    {
        return query(site<<1,(l == st[site].l ? l : st[site].l+ans[h][l-1]),st[site].l-1+ans[h][r],k,h+1);
    }
    else
    {
        return query(site<<1|1,(l == st[site].l ? mid+1 : mid+l-st[site].l+1-ans[h][l-1]),mid+r-st[site].l+1-ans[h][r],k-(ans[h][r]-(l == st[site].l ? 0 : ans[h][l-1])),h+1);
    }
}

int main()
{
    int i,n,m,l,r,k;
    while(scanf("%d %d",&n,&m) != EOF)
    {
        for(i = 1; i <= n; ++i)
        {
            scanf("%d",&num[i]);
            s[i] = num[i];
        }

        sort(s+1,s+n+1,cmp);

        for(i = 1; i <= n; ++i)
        {
            stn[0][i] = num[i];
        }

        Init_St(1,1,n,0);

        while(m--)
        {
            scanf("%d %d %d",&l,&r,&k);
            printf("%d\n",query(1,l,r,k,0));
        }
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值