原数列:8 9 1 4 78 56 4 10 7 5
排序后:1 4 4 5 7 8 9 10 56 78
划分树:(蓝色表示将要进入左子树)
[8 9 1 4 78 56 4 10 7 5]
[1 4 4 7 5 ] [8 9 78 56 10]
[1 44] [7 5] [89 10 ] [78 56]
[1 4][4][5][7] [8 9 ] [10] [56][78]
记录最左边到当前结点进入左子树的个数
[0 0 1 2 2 2 3 3 4 5]
[1 2 3 3 3] [4 5 5 5 6]
[1 2 2][2 3] [4 5 5][ 5 6]
对应的线段树区间:
[1,10]
[1,5][6,10]
[1,3][4,5][6,8][9,10]
划分树是类似快排的思想,建立在线段树(区间记为[L,R])的基础上,先将原数列排序(记为SortNum),每次将当前数的跟SortNum[mid](mid=( L + R)>>1)比较,小的进入左子树,大的进入右子树,并记录最左边到当前结点进入左子树的个数。
查找:
设当前要查找的区间为[s,t],当前所在线段树区间为[L,R]
进入左子树:
则记 x= toleft[row][s - 1] - toleft[row][L – 1],即为[L,s-1]区间里进入左子树的数的个数,记y= toleft[row][t] - toleft[row][L – 1],即为区间[L,t]区间进入右子树的数的个数,假如k<=(y -x)即小于[s,t]区间进入左子树的个数,则查找左子树的区间[L+ x, L+ y -1];
进入右子树:
记rx为[L,s-1]区间里进入右子树的数的个数,ry为区间[L,t]区间进入右子树的数的个数
则rx= s – L -x,ry= t – L -y,假如k> (y – x)即大于[s,t]区间进入左子树的个数,则查找右子树
的区间[mid+ 1 + rx, mid + 1 + ry],并更新k= k – (y - x)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAXN 100000
int num[MAXN + 10];
int SortNum[MAXN + 10];
int value[20][MAXN + 10];
int toleft[20][MAXN + 10];
void build_tree(int s,int t,int row)
{
if(s == t)
{
return;
}
int mid = (s + t)>>1;
int same;
int lchild = s;
int rchild = mid + 1;
int i;
same = mid - s + 1;
for(i = s;i <= t;i++)
{
if(value[row][i] < SortNum[mid])
{
same--;
}
}//记录跟SortNum[mid]一样的数将进入左子树的个数
for(i = s;i <= t;i++)
{
toleft[row][i] = toleft[row][i - 1];
if(value[row][i] < SortNum[mid])//进入左子树
{
value[row + 1][lchild++]= value[row][i];
toleft[row][i]++;
}
else if(value[row][i] > SortNum[mid])//进入左子树
{
value[row + 1][rchild++] = value[row][i];
}
else if(same > 0)//跟SortNum[mid]一样的数进入左子树
{
value[row + 1][lchild++] = value[row][i];
toleft[row][i]++;
same--;
}
else//跟SortNum[mid]一样的数进入右子树
{
value[row + 1][rchild++] = value[row][i];
}
}
build_tree(s,mid,row + 1);
build_tree(mid + 1,t,row + 1);
}
int query(int L,int R,int s,int t,int k,int row)
{
// cout<<s<<" "<<t<<endl;
if(s == t)
{
return value[row][s];
}
int x,y;
int rx,ry;
int mid = (L + R) >> 1;
x = toleft[row][s - 1] - toleft[row][L - 1];//[L,s-1]区间里进入左子树的数的个数
y = toleft[row][t] - toleft[row][L - 1];//区间[L,t]区间进入右子树的数的个数
if(y - x >= k)//y-x为[s,t]区间进入左子数的个数
{
return query(L,mid,L + x,L + y -1,k,row + 1);
}
else
{
rx = s - L -x;//[L,s-1]区间里进入右子树的数的个数
ry = t - L - y;//区间[L,t]区间进入右子树的数的个数
return query(mid + 1,R,mid + 1 + rx,mid + 1 + ry,k - (y -x),row + 1);
}
}
int main()
{
int n,m;
int i;
int s,t,k;
int ans;
while(scanf("%d %d",&n,&m) !=EOF)
{
memset(toleft,0,sizeof(toleft));
for(i = 1;i <= n;i++)
{
scanf("%d",&value[0][i]);
}
memcpy(SortNum,value,sizeof(value[0]));
sort(&SortNum[1],&SortNum[n + 1]);
build_tree(1,n,0);
for(i = 0;i < m;i++)
{
scanf("%d %d %d",&s,&t,&k);
ans = query(1,n,s,t,k,0);
printf("%d\n",ans);
}
}
return 0;
}