划分树
划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大或第k小值 。
树的结构:
int sorted[N]; // 对原来集合中的元素排序后的值。
struct tree{ int val[N]; // val 记录第 k 层当前位置的元素的值
int num[N]; // num 记录元素所在区间的当前位置之前进入左孩子的个数
}t[20];
计算3到9区间的第二小值:
首先[3,9]区间之间有3个在左子树比k值大,所以跳到左子树中(大区间),小区间就变成[2,4],然后递归找到区间只剩一个值为止。
怎么找到的:
首先根据num数组我能知道[3,9]之间有多少个数进入了左子树。这样根据这个数来判断我们要的值是在左还是右。这样大区间就确定了。
然后,小区间的2是根据[1,3)之间有多少个进入左子树(值为1)的加上大区间的左端点(值为1);
小区间的右端点(4)是让小区间左端点(2)+[3,9]之间有多少个数进入左子树(有3个)-1;
现在我们判断如果是右子树的话。
比如是找第4小值:
首先[3,9]之间有3个在左子树比k小,所以跳到右子树。小区间是[7,10]
小区间的7是mid(5)(中间的一个数)+1+小区间的3-大区间的1(这个是计算区间之间右多少个数)-1([1,3)之间进入左子树个数);
小区间的10是7+小区间的右端点9-小区间的左端点3+1(这个计算出小区间有多少数)-3(左子树进入了多少个小区间内的数);最后将k-3(左子树进入了多少个小区间内的数);
- #include <cstdio>
- #include <iostream>
- #include <cstring>
- #include <queue>
- #include <cmath>
- #include <algorithm>
- using namespace std;
- const int N=100005;
- int sorted[N]; // 对原来集合中的元素排序后的值。
- int arr[N];
- double Sum=0;
- struct tree{
- int val[N]; // val 记录第 k 层当前位置的元素的值
- int num[N]; // num 记录元素所在区间的当前位置之前进入左孩子的个数
- }t[20];
- //划分树构建
- void build(int l,int r,int p){
- if(l==r) return ;
- /* same 用来标记和中间值 sorted[mid] 相等的,且分到左孩子的数的个数。
- 初始时,假定当前区间[lft,rht]有 mid-lft+1 个和 sorted[mid] 相等。
- 先踢掉比中间值小的,剩下的就是要插入到左边的
- 例如 1 3 3 3 3 5 5 7 same=4,sorted[mid]=3,分到左孩子且值为3的个数为3
- */
- int mid=(l+r)>>1,same=mid-l+1,lp=l,rp=mid+1;
- for(int i=l;i<=r;i++){
- if(t[p].val[i]<sorted[mid]) same--;
- }
- for(int i=l;i<=r;i++){
- if(i==l){ // 初始一个子树。
- t[p].num[i]=t[p].sum[i]=0;
- }
- else{ // 初始区间下一个节点。
- t[p].num[i]=t[p].num[i-1];
- }
- /* 如果大于,肯定进入右孩子,否则,判断是否还有相等的应该进入左孩子的,
- 没有,就直接进入右孩子,否则进入左孩子,同时更新节点的 num 域*/
- if(t[p].val[i]<sorted[mid]){
- t[p].num[i]++;
- t[p+1].val[lp++]=t[p].val[i];
- }
- else if(t[p].val[i]>sorted[mid]){
- t[p+1].val[rp++]=t[p].val[i];
- }
- else{
- if(same){
- same--;
- t[p].num[i]++;
- t[p+1].val[lp++]=t[p].val[i];
- }
- else
- t[p+1].val[rp++]=t[p].val[i];
- }
- }
- build(l,mid,p+1);
- build(mid+1,r,p+1);
- }
- //划分树查找
- /* 在区间[a, b]上查找第 k 小元素。*/
- int query(int a,int b,int l,int r,int p,int k){
- int s; //[l, a)内将被划分到左子树的元素数目
- int ss; //[a, b]内将被划分到左子树的元素数目
- int mid=(l+r)>>1;
- if(l==r) return t[p].val[a];
- //区间端点点重合的情况,要单独考虑 !!!!!!
- if(a==l){
- s=0;
- ss=t[p].num[b];
- }
- else{
- s=t[p].num[a-1];
- ss=t[p].num[b]-s;
- }
- // 进入左孩子,同时更新区间端点值
- if(ss>=k){
- int la=l+s;
- int lb=l+s+ss-1;
- return query(la,lb,l,mid,p+1,k);
- }
- else{
- int la=mid+1+a-l-s;
- int lb=mid+1+b-l-s-ss; //lb=la+b-a-num[b]=mid+1+a-l-s+b-a-s-ss
- return query(la,lb,mid+1,r,p+1,k-ss);
- }
- }
- int main() {
- #ifndef ONLINE_JUDGE
- freopen("in.txt","r",stdin);
- #endif
- int n,m,k,a,b;
- while(~scanf("%d%d",&n,&m)){
- for(int i=1;i<=n;i++){
- scanf("%d",&arr[i]);
- t[0].val[i]=sorted[i]=arr[i];
- }
- sort(sorted+1,sorted+n+1);
- build(1,n,0);
- while(m--){
- scanf("%d%d%d",&a,&b,&k);
- printf("%d\n",query(a,b,1,n,0,k));
- }
- }
- }