划分树

#include<cstdio>
#include<algorithm>
using namespace std;

#define MAX 100000

int tree[20][MAX+50];     //第一维存原值,后来的存划分后的左右子树的值
int toLeft[20][MAX+50];   //代表当前层区间[left,right]进入左子树的数目
int sorted[MAX+50];       //排序好的数组

/**
* 建树函数
* level: 当前层数
* left: 当前层第一个元素的下标
* right: 当前层最后一个元素的下标
*/
void build_tree(int level,int left,int right) {
	if(left==right) return ;//当区间内只有一个元素时无须再划分
	 
	int mid=(left+right)>>1;//[left,mid]是下一层的左子树, [mid+1,right]是下一层的右子树,mid是左子树的最后一个元素的下标 
	
	int suppose=mid-left+1;//suppose是该层中位数被分配到左子树的个数,这里猜测它是一个最大可能的数,mid-left+1就是左子树的元素个数 
	for(int i=left; i<=right; i++)
		if(tree[level][i]<sorted[mid])//suppose要减去该层所有元素里小于中位数的个数 ,因为小于中位数的数一定会分到左子树,而他们不是中位数,所以中位数分配到左子树的个数会减去这些数的个数,这样就得到了正确的suppose值 
			suppose--;
			
	int subLeft=left,subRight=mid+1;//subLeft是左子树的第一个元素的下标, subRight是右子树的第一个元素的下标
	for(int i=left; i<=right; i++) {//对当前层每个元素进行划分,根据划分算法分到左子树或右子树
		if(i==left)
			toLeft[level][i]=0;//如果是第一个元素,那么设[left,i]区间内分配到左子树的元素数量为0
		else
			toLeft[level][i]=toLeft[level][i-1];//如果不是第一个元素,那么设[left,i]区间内分配到左子树的元素数量为[left,i-1]区间内分配到左子树的元素数量
			
		if(tree[level][i]<sorted[mid] || tree[level][i]==sorted[mid]&&suppose>0) {//如果元素小于中位数,或者元素等于中位数并且剩余分配到左子树的中位数的名额>0,就把元素分配到左子树
			tree[level+1][subLeft++]=tree[level][i];//将元素分配到左子树
			toLeft[level][i]++;//[left,i]区间内分配到左子树的元素数量+1
			if(tree[level][i]==sorted[mid])//如果分配到左子树的元素是中位数,那么剩余分配到左子树的中位数的名额-1
				suppose--;
		} else
			tree[level+1][subRight++]=tree[level][i];//将元素分配到右子树
	}
	
	build_tree(level+1,left,mid);//划分左子树 
	build_tree(level+1,mid+1,right);//划分右子树 
}
/**
* 查询函数
* level: 当前层数
* left: 当前层第一个元素的下标
* right: 当前层最后一个元素的下标
* qleft: 查询区间中第一个元素的下标
* qright: 查询区间中最后一个元素的下标
* k: 查询结果是查询区间中第k大的值
*/
int query(int level,int left,int right,int qleft,int qright,int k) {
	if(qleft==qright)
		return tree[level][qleft];//查询区间中只有一个元素,那么就返回这个元素
	
	int mid=(left+right)>>1;//[left,mid]是下一层的左子树, [mid+1,right]是下一层的右子树,mid是左子树的最后一个元素的下标
	
	int lef;    //lef是区间[left,qleft-1]中进入左子树的个数(就是当前层中查询区间左边的元素进入左子树的个数)
	int tolef;  //tolef是区间[qleft,qright]中进入左子树的个数(就是当前层中查询区间内的元素进入左子树的个数)
	if(qleft==left)
		lef=0,tolef=toLeft[level][qright];//如果查询区间的第一个元素下标=当前层第一个元素的下标,那么当前层中查询区间左边没有元素,所以lef=0
	else
		lef=toLeft[level][qleft-1],tolef=toLeft[level][qright]-lef;//利用前缀和思想计算tolef:toLeft[level][qright](查询区间左边+查询区间内)-lef(查询区间左边)=tolef(查询区间内)
	
	if(k<=tolef) {//我们要的是查询区间内第k大的数,如果k<=tolef,即查询区间内的元素进入左子树的个数>=k,那么我们的答案一定在左子树中
		int newleft=left+lef;//左子树第一个元素的下标+查询区间左边的元素进入左子树的个数=当前层查询区间的第一个元素的下标在左子树中对应的下标
		int newright=newleft+tolef-1;  //当前层查询区间的第一个元素的下标在左子树中对应的下标+当前层中查询区间内的元素进入左子树的个数-1=当前层查询区间的最后一个元素的下标在左子树中对应的下标
		return query(level+1,left,mid,newleft,newright,k);//在左子树中查询
	} else {
		int newleft=mid+qleft-left-lef+1;
		//qleft-left为当前层中查询区间左边的元素数量
		//qleft-left-lef为当前层中查询区间左边区间里存到右子树的元素的个数(总元素个数-存进左子树的个数)
		//(mid+1)+(qleft-left-lef)为当前层查询区间的第一个元素的下标在右子树中对应的下标(右子树第一个元素的下标+当前层中查询区间左边区间里存到右子树的元素的个数)
		int newright=newleft+qright-qleft+1-tolef-1;
		//qright-qleft+1为当前层中查询区间内的元素数量
		//qright-qleft+1-tolef为为当前层中查询区间里存到右子树的元素的个数(总元素个数-存进左子树的个数)
		//newleft+(qright-qleft+1-tolef)-1为当前层查询区间的最后一个元素的下标在右子树中对应的下标(当前层查询区间的第一个元素的下标在右子树中对应的下标+当前层中查询区间里存到右子树的元素的个数-1)
		return query(level+1,mid+1,right,newleft,newright,k-tolef);//在右子树中查询
	}
}

int main() {
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) {
		scanf("%d",&tree[0][i]);
		sorted[i]=tree[0][i];
	}
	
	sort(sorted+1,sorted+n+1);//排序以便在建树时 多次获得不同区间的中位数
	
	build_tree(0,1,n);//建树,当前层数0,第一个元素下标为1,最后一个元素下标为n
	
	for(int i=0; i<m; i++) {
		int l,r,k;//输入l,r,k,指求出在下标l~r区间内第k大的数,[l,r]是查询区间
		scanf("%d%d%d",&l,&r,&k);
		int ans=query(0,1,n,l,r,k);//在第0层[1,n]区间内(就是题目给出的整个数组范围)查询区间[l,r]中第k大的数
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值