POJ 2104 K-th Number 划分树

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

using namespace std;

struct Parti_tree{
    #define MID(x,y) ( ( x + y ) >> 1 )
    #define L(x) ( x << 1 )
    #define R(x) ( x << 1 | 1 )
    static const int MAX = 100010;
	struct Tnode{
	    int l,r;
	    inline int len() { return r - l;}
	    inline int mid() { return MID(l,r);}
	    inline bool in(int ll,int rr) { return l >= ll && r <= rr; }
	    inline void lr(int ll,int rr){ l = ll; r = rr;}
	};
	Tnode node[MAX<<2];
	int Left[20][MAX];//保存每个元素前面的元素进入左子树的个数(不包括自己)
	int seg[20][MAX];//保存每层的元素
	int sa[MAX];//保存排序好的数组,范围:1 - n
	void init(){
		memset(Left,0,sizeof(Left));
		memset(node,0,sizeof(node));
	}
	void build(int s,int t){
	    sort(sa+1,sa+t+1);
	    Parti_build(1,s,t,1);
    }
    int find(int s,int t,int k){
        return find_rank(1,s,t,1,k);
    }
	void Parti_build(int t,int l,int r,int d)
	{//t节点编号,l,r节点控制的范围,d层数
		node[t].lr(l, r);
		if( node[t].len() == 0 ) return ;
		int mid = MID(l, r);
		int lsame = mid - l + 1;
		//初始化能进入左子树且和中间元素相等的值
		for(int i=l; i<=r; i++)
			if(seg[d][i] < sa[mid])
				lsame--;
		int lpos = l;//左子树开始位置
		int rpos = mid+1;//右子树开始位置
		int same = 0;//进入左子树且和mid相等的元素的数目
		for(int i=l; i<=r; i++)
		{
			if( i == l )
				Left[d][i] = 0;
			else
				Left[d][i] = Left[d][i-1];
			if( seg[d][i] < sa[mid] ){
				Left[d][i]++;//进入左子树,更新left
				seg[d+1][lpos++] = seg[d][i];
			}
			else if(seg[d][i] > sa[mid])//进入右子树
				seg[d+1][rpos++] = seg[d][i];
			if(seg[d][i] == sa[mid])
				if(same < lsame){
					same++;//还剩位置,进入左子树,更新left
					Left[d][i]++;
					seg[d+1][lpos++] = seg[d][i];
				}
				else//否则进入右子树
					seg[d+1][rpos++] = seg[d][i];
		}
		Parti_build(L(t), l, mid, d+1);
		Parti_build(R(t), mid+1, r, d+1);
	}

	int find_rank(int t,int L,int R,int d,int k)
	{
		if( node[t].len() == 0 )
			return seg[d][L];//区间长度为1,找到元素
		int ss;//ss表示从[l,L-1]有多少个小于sa[mid]的数被分到左边
		if( L == node[t].l )
			ss = 0;
		else
            ss = Left[d][L-1];
        int s = Left[d][R] - ss;//s表示区间[L,R]有多少个小于sa[mid]的数被分到左边

		if(s >= k)//进入左子树的个数大于k,那第k小在左子树
			return find_rank(L(t), node[t].l+ss, node[t].l+ss+s-1, d+1, k);
		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(R(t), mid+bb+1, mid+bb+b,d+1,k-s);
		}
	}
};

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

	while( ~scanf("%d%d",&n,&m) )
	{
		t.init();
		for(int i=1; i<=n; i++)
		{
			scanf("%d",&t.sa[i]);
			t.seg[1][i] = t.sa[i];
		}
		t.build(1,n);
		while( m-- )
		{
			scanf("%d%d%d",&x,&y,&k);
			int ans = t.find(x, y, k);
			printf("%d\n",ans);
		}
	}

    return 0;
}

题意:给出长度为n的序列。给出m个操作,每个操作是求出区间[l,r]中第k小的数字。

思路:标准的划分树,直接上代码吧。

代码如下:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值