划分树

简介

划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值。

划分树的基本思想就是对于某个区间,把它划分成两个子区间,左边区间的数小于等于右边区间的数。

查找的时候通过记录进入左子树的数的个数,确定下一个查找区间,最后范围缩小到1,就找到了。


建树

建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数a[mid],小于a[mid]的数划入他的左子树[l,mid-1],大于它的划入右子树[mid,r]。同时,对于第i个数,记录[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间[l,mid-1]和右子树区间[mid,r]递归的继续建树就可以了。建树的时候要注意对于被分到同一子树的元素,元素间的相对位置不能改变。


void build(int left,int right,int d){
	if(left==right) return;
	int mid=(left+right)>>1;
	int need=mid-left+1,same=0,ln=left,rn=mid+1;
	for(int i=left;i<=right;i++)
		if(t[d].val[i]<sorted[mid]) 
			need--;
	for(int i=left;i<=right;i++){
		if(i==left)	
			t[d].num[i]=0,t[d].sum[i]=0;
		else	 
			t[d].num[i]=t[d].num[i-1],t[d].sum[i]=t[d].sum[i-1];

		if(t[d].val[i]<sorted[mid]){//划入左区间
			t[d].num[i]++;
			t[d].sum[i]+=t[d].val[i];
			t[d+1].val[ln++]=t[d].val[i];
		}
		else if(t[d].val[i]>sorted[mid])
			t[d+1].val[rn++]=t[d].val[i];
		else{//val相同,使左右个数平衡
			same++;
			if(need>=same){	//划入左区间
				t[d].num[i]++;
				t[d].sum[i]+=t[d].val[i];
				t[d+1].val[ln++]=t[d].val[i];
			}
			else
				t[d+1].val[rn++]=t[d].val[i];
		}
	}
	build(left,mid,d+1);
	build(mid+1,right,d+1);
}



划分树模板(POJ 2104)

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

typedef long long LL;
const int N=1e5+5;  
int sorted[N];
LL sum;//区间内比第k个小的和
struct node{  
         int val[N];//val记录第k层当前位置元素的值  
         int num[N];//num记录自己第几个进入左区间
		 LL sum[N];//sum记录自己进入区间后的区间和
}t[20];

void build(int left,int right,int d){
	if(left==right) return;
	int mid=(left+right)>>1;
	int need=mid-left+1,same=0,ln=left,rn=mid+1;
	for(int i=left;i<=right;i++)
		if(t[d].val[i]<sorted[mid]) 
			need--;
	for(int i=left;i<=right;i++){
		if(i==left)	
			t[d].num[i]=0,t[d].sum[i]=0;
		else	 
			t[d].num[i]=t[d].num[i-1],t[d].sum[i]=t[d].sum[i-1];

		if(t[d].val[i]<sorted[mid]){//划入左区间
			t[d].num[i]++;
			t[d].sum[i]+=t[d].val[i];
			t[d+1].val[ln++]=t[d].val[i];
		}
		else if(t[d].val[i]>sorted[mid])
			t[d+1].val[rn++]=t[d].val[i];
		else{//val相同,使左右个数平衡
			same++;
			if(need>=same){	//划入左区间
				t[d].num[i]++;
				t[d].sum[i]+=t[d].val[i];
				t[d+1].val[ln++]=t[d].val[i];
			}
			else
				t[d+1].val[rn++]=t[d].val[i];
		}
	}
	build(left,mid,d+1);
	build(mid+1,right,d+1);
}

int query(int st,int ed,int k,int left,int right,int d){
	if(left==right) return t[d].val[left];
	int ly,lx,ry,rx,mid=(left+right)>>1;  
    LL tsum=0;
	if(st==left){
		lx=0;//lx 记录区间[left,st-1]中进入左孩子的元素的个数 
		ly=t[d].num[ed];//ly 记录区间[st,ed]中进入左孩子的元素的个数 
		tsum=t[d].sum[ed];//tsum 记录区间[st,ed]中小于第k大的元素的值和 
	}
	else{
		lx=t[d].num[st-1];
		ly=t[d].num[ed]-t[d].num[st-1];
		tsum=t[d].sum[ed]-t[d].sum[st-1];
	}

	//进入下一层
	if(ly>=k){//在左区间
		st=left+lx;//left+[lx,lx+ly-1]是区间的范围
		ed=left+lx+ly-1;
		return query(st,ed,k,left,mid,d+1);
	}
	else{
		rx=st-left-lx;//[left,st-1]-lx=rx 表示[left,st-1]中分到右孩子的个数 
		ry=ed-st+1-ly;//[st,ed]-ly=ry 表示[st,ed]中分到右孩子的个数 
		st=mid+1+rx;//mid+1+[rx,rx+ry-1]是区间的范围
		ed=mid+1+rx+ry-1;
		sum+=tsum;	
		return query(st,ed,k-ly,mid+1,right,d+1);
	}
}
int main(){
	int n,m;
	while(cin>>n>>m){
		for(int i=1;i<=n;i++){
			scanf("%d",&sorted[i]);
			t[0].val[i]=sorted[i];
		}
		sort(sorted+1,sorted+1+n);
		build(1,n,0);
		for(int i=0;i<m;i++){
			int l,r,c;
			sum=0;
			scanf("%d %d %d",&l,&r,&c);
			int ans=query(l,r,c,1,n,0);
			printf("%d\n",ans,sum);
		}
	}
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值