pku2104 第k大数-划分树做法

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

题意:求任意区间第k大数

 

分析:划分树主要参考了大牛博客

http://www.notonlysuccess.com/?p=142

http://blog.sina.com.cn/s/blog_5f5353cc0100ki2e.html

划分树兼容了快排和归并的特点。。。采用划分的思想逐渐往下划,最后叶子节点的元素就是排好序的元素。。。其实可以理解成稳定的快排。。。和归并树一样,记录每一层的结果。。。并记录toleft[]。。。

查找的过程,总是查询[ tt[p].l [ l, r] tt[p].r ]。。。这里就有了几个区间,想想,划分树划分下去的时候[tt[p].l, l-1]中不大于中位数的一定靠在最前,[l, r]中不大于中位数的排在其后。。。我们通过计算[l, r]中不大于中位数的个数l2来和k比较,来确定是到左子树还是右子树去找,查找的范围都可以通过两个区间划分到左子树的个数来缩小。。。。

 

时间复杂度nlgn+mlgn,比归并树nlgn+m(lgn)^3快多了,m越大效果越好。。

代码:

比归并树快一倍吧。。。

#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;

const int N=100010;
const int DEEP=20;
int n, m, sa[N];
int seq[DEEP][N]; //归并树
int toleft[DEEP][N]; //记录当前层左边界到i位置有多少划入左子树
struct node
{
	int l, r, mid;
} tt[N*4];

void build(int l, int r, int d, int p)
{
	tt[p].l = l;
	tt[p].r = r;
	tt[p].mid = (l+r)>>1;
	if(l==r)
		return;

	int lsame, i, j, ii;
	lsame = tt[p].mid-tt[p].l+1;
	for(i=tt[p].l; i<=tt[p].r; i++)
		if(seq[d][i]<sa[tt[p].mid])
			lsame--;
	i = tt[p].l;
	j = tt[p].mid+1; 
	for(ii=tt[p].l; ii<=tt[p].r; ii++)
	{
		if(ii==tt[p].l)
			toleft[d][ii] = 0;
		else
			toleft[d][ii] = toleft[d][ii-1];
		if(seq[d][ii]<sa[tt[p].mid])
		{
			toleft[d][ii]++;
			seq[d+1][i++] = seq[d][ii];
		}
		else if(seq[d][ii]>sa[tt[p].mid])
		{
			seq[d+1][j++] = seq[d][ii];
		}
		else
		{
			if(lsame>0)
			{
				toleft[d][ii]++;
				lsame--;
				seq[d+1][i++] = seq[d][ii];
			}
			else
			{
				seq[d+1][j++] = seq[d][ii];
			}
		}
	}
	build(l, tt[p].mid, d+1, p*2);
	build(tt[p].mid+1, r, d+1, p*2+1);
}
int query(int l, int r, int k, int d, int p)
{
	if(l==r)
		return seq[d][l];

	//l1为[tt[p].l, l-1]之间划进左子树的个数,l2为[l, r]之间划进左子树的个数。。
	int l1, l2; 
	if(l==tt[p].l)
	{
		l1 = 0;
		l2 = toleft[d][r];
	}
	else
	{
		l1 = toleft[d][l-1];
		l2 = toleft[d][r]-toleft[d][l-1];
	}
	int ll, rr;
	if(k<=l2)
	{
		ll = tt[p].l + l1;
		rr = tt[p].l + l1+l2 -1;
		return query(ll, rr, k, d+1, p*2);
	}
	else
	{
		//r1表示[tt[p].l, l]之间划进右子树的个数,r2为[l, r]之间划进左子树的个数。。
		int r1, r2;
		r1 = l-tt[p].l-l1;
		r2 = r-l+1-l2;
		ll = tt[p].mid+1 + r1;
		rr = tt[p].mid+1 + r1+r2 -1;
		return query(ll, rr, k-l2, d+1, p*2+1);
	}
}

int main()
{
	int i, j, x, y, k;
	while(scanf("%d%d", &n, &m)!=EOF)
	{
		for(i=0; i<n; i++)
		{
			scanf("%d", &seq[0][i]);
			sa[i] = seq[0][i];
		}
		sort(sa, sa+n);
		build(0, n-1, 0, 1);
		
		while(m--)
		{
			scanf("%d%d%d", &x, &y, &k);
			printf("%d\n", query(x-1, y-1, k, 0, 1));
		}
	}

	return 0;
}


 

你可能感兴趣的:(pku2104 第k大数-划分树做法)