pku2140任意区间第k小数-归并树

 

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

 

题意:给你一个数组,求任意区间[i, j]的第k小的数。。。(没有修改)

 

 

分析:

PS:高级数据结构真难学。。显然我是创造不出来这些高级数据结构的了。归并树。。网上到处学习,网上各种解释,都不怎么看得懂。。。主要参考了http://blog.sina.com.cn/s/blog_5f5353cc0100kh6b.html学习了代码。。。orz,弄代码才懂的。。。

 http://yueyue1105.blog.163.com/blog/static/431117682010716111425892/这个地方讲得比较清楚,介绍了归并树和划分树。。。

 

 

简单解释:二分+归并排序+线段树+二分

首先建树:按照归并排序的过程,先走到底,然后将该层(deep)[l...r]归并后得到的数据存到seq[deep][l...r]中去,则这一小段始终是有序的。。。。可以想象,最上层是排好序的数组,所有叶子节点组合起来就是原数组。。。

查询分三个步骤:1是在最上层二分选取一个元素key,查询key在[l, r]中大于几个数num,取得一个最小的num>=k的那个,则为所求,通过线段树去查询key在[l, r]中大于几个数num,到了符合的区间后就可以通过二分来求得其在本区间大于几个数。。。貌似说不清楚了。。

 

归并树代码:

#include<iostream>
using namespace std;

const int N=100010;
const int DEEP=20;

int seq[DEEP][N]; //归并树
int a[N], n, m;
struct node
{
	int l, r, mid;
} tree[N*4]; //线段树

//线段树+归并树一起建了,实际上二者的过程一样。。
void build(int l, int r, int p, int deep)
{
	tree[p].l = l;
	tree[p].r = r;
	tree[p].mid = (l+r)>>1;
	if(l==r)
	{
		seq[deep][l] = a[l];
		return;
	}
	build(l, tree[p].mid, p*2, deep+1);
	build(tree[p].mid+1, r, p*2+1, deep+1);

	//归并过程
	int i, j, k;
	for(i=l, j=tree[p].mid+1, k=l; i<=tree[p].mid && j<=r; )
	{
		if(seq[deep+1][i]>seq[deep+1][j])
			seq[deep][k++] = seq[deep+1][j++];
		else
			seq[deep][k++] = seq[deep+1][i++];
	}
	while(i<=tree[p].mid)
		seq[deep][k++] = seq[deep+1][i++];
	while(j<=r)
		seq[deep][k++] = seq[deep+1][j++];
}

//通过二分枚举,返回key在本区间大于多少个数
//注意:任何一个通过线段树最终到达的区间一定是已经排好序了的,所以可以通过二分求
int counthelp(int l, int r, int p, int key, int deep)
{
	int mid;
	while(l<=r)
	{
		mid = (l+r)>>1;
		if(seq[deep][mid]<key)
			l = mid+1;
		else
			r = mid-1;
	}
	return r-tree[p].l+1;
}
//返回key在[l, r]总区间内大于几个数
int count(int l, int r, int p, int key, int deep)
{
	if(tree[p].l==l && tree[p].r==r)
	{
		return counthelp(l, r, p, key, deep);
	}
	if(r<=tree[p].mid)
		return count(l, r, p*2, key, deep+1);
	else if(l>tree[p].mid)
		return count(l, r, p*2+1, key, deep+1);
	else
	{
		return count(l, tree[p].mid, p*2, key, deep+1)+count(tree[p].mid+1, r, p*2+1, key, deep+1);
	}
}

//返回在通过二分枚举后得到的结果。。。
int query(int ll, int rr, int cnt)
{
	int mid, tmp;
	int l = 1;
	int r = n;
	while(l<=r)
	{
		mid = (l+r)>>1;
		tmp = count(ll, rr, 1, seq[1][mid], 1); 
		if(tmp>=cnt)
			r = mid-1;
		else
			l = mid+1;
	}
	return seq[1][l-1];
}

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


 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值