首页 > 代码库 > poj 2401 划分树 求区间第k大的数

poj 2401 划分树 求区间第k大的数

题目:http://poj.org/problem?id=2104

划分树待我好好理解下再写个教程吧,觉得网上的内容一般,,,

模板题:
贴代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a) memset(a,0,sizeof(a))

const int MAXN = 100010;

struct Node
{
    int l,r;
    int len(){return r-l;}
    int mid(){return (l+r)/2;}
    bool in(int ll,int rr){return l>=ll && r<=rr;}
    void lr(int ll,int rr){l = ll; r=rr;}
}node[MAXN*4];

int num_left[20][MAXN],seg[20][MAXN],sa[MAXN];
//sa数组存sort后的结果
//seg数组存的是d层划分后的数字 (类似快排Partation (d-1)次后的结果)
//num_left存的是d层在i之前(包括i)小于 sa[mid] 的数的数目

void Init()
{
    //CLR(seg),CLR(num_left),CLR(node);
    		memset(seg,0,sizeof(seg));
		memset(num_left,0,sizeof(num_left));
		memset(node,0,sizeof(node));
}
void PaBuild(int t,int l, int r,int d)
{
    node[t].lr(l,r);
    if(node[t].len() == 0)return;
    int mid=(l+r)/2,lsame=mid-l+1;
    for(int i=l;i<=r;i++)//首先确定分到每一侧的数的数目
        if(seg[d][i] < sa[mid])//因为相同的元素可能被分到两侧
            lsame--;
    int lpos=l,rpos=mid+1;
    for(int i=l;i<=r;i++)
    {
        if(i == l)
            num_left[d][i]=0;
        else
            num_left[d][i]=num_left[d][i-1];
        if(seg[d][i] < sa[mid])
        {
            num_left[d][i]++;
            seg[d+1][lpos++]=seg[d][i];
        }
        if(seg[d][i] > sa[mid])
            seg[d+1][rpos++] = seg[d][i];
        if(seg[d][i] == sa[mid])
            if(lsame > 0)// 如果大于0,说明左侧可以分和sa[mid]相同的数字
            {
                lsame--;
                num_left[d][i]++;
                seg[d+1][lpos++]=seg[d][i];
            }
            else    //反之,说明左侧数字已经分满了,就分到右边去
                seg[d+1][rpos++]=seg[d][i];
    }
    PaBuild(t*2,l,mid,d+1);
    PaBuild(t*2+1,mid+1,r,d+1);
}
void Build(int s,int t)
{
    sort(sa+s,sa+t+s);
    PaBuild(1,s,t,1);
}

int find_rank(int t,int l,int r,int d,int val)//val指的是k
{
    if(node[t].len() == 0)return seg[d][l];
    int s,ss;   //s表示区间[l,r]有多少个小于sa[mid]的数被分到左边
    if( l == node[t].l)//ss表示从当前区间的L到l-1有多少个小于sa[mid]的数被分到左边,L,R指的是树上当前节点的区间范围
        ss=0;
    else
        ss=num_left[d][l-1];
    s=num_left[d][r]-ss;
    if(s>=val)
        return find_rank(t*2, node[t].l+ss,node[t].l+ss+s-1,d+1,val);
    else
    {
        int mid = node[t].mid();
        int bb=l-node[t].l-ss;  //表示从当前区间L到l-1有多少个分到右边
        int b=r-l+1-s;          //表示[l,r]有多少个分到右边
        return find_rank(t*2+1,mid+bb+1,mid+bb+b,d+1,val-s);
    }
}

int Query(int s,int t,int k)
{
    return find_rank(1,s,t,1,k);
}

int main()
{
    int n,m,x,y,k;

    while(~scanf("%d%d",&n,&m))
    {
        Init();
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sa[i]);
            seg[1][i] = sa[i];
        }

        Build(1,n);
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&k);
            int ans=Query(x,y,k);
            printf("%d\n",ans);
        }
    }
    return 0;
}