原数列: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; }