简介
划分树是一种基于线段树的数据结构。主要用于快速求出(在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;
}