划分树

首先,Orz一下AHdoc神犇,本沙茶是看他的总结才搞懂划分树的。
原文地址

划分树,就是每个结点代表一个序列,设这个序列的长度为len,若len>1,则序列中前len/2(上取整,这里数学公式不好打,真囧)个元素被分到左子结点,左子结点代表的序列就是这些元素 按照根结点中的顺序组成的序列,而剩下的元素被分到右子结点,右子结点代表的序列也就是剩下的元素按照根结点中的顺序组成的序列;若len=1,则该结点为叶结点。划分树最重要的应用就是找区间第K小(只是查找,不包括修改)。

写划分树时主要有两个函数:建树和找区间第K小。由于后者AHdoc神犇已经总结了,所以这里只总结建树的函数。

设目前结点为[l..r](l<r,就是目前的结点是原序列不断划分后得到[l..r]这一段,其实也就是a0[l..r]打乱顺序后得到的,a0为原序列递增排序后的序列)首先找到中值,就是a0[mid],mid=l+r>>1。然后可以得到,[l..r]中小于中值的的元素一定被划分到左子结点,[l..r]中大于中值的元素一定被划分到右子结点,而[l..r]中等于中值的元素则不确定,有的被划分到左子结点,有的被划分到右子结点,这就需要先找到应被划分到左子结点的等于中值的元素个数sm(从mid开始不断往左,直到找到边界处或者找到一个小于中值的元素为止,或者说,sm就是a0[l..mid]中等于中值的元素个数),然后开始划分,小于中值分到左子结点,大于中值分到右子结点,等于中值的,若目前还没满sm则分到左子结点否则分到右子结点。另外中间有两个值需要记录(找区间第K小时必须要用到):sl和sr。sl[i]表示[l..i]中被分到左子结点的元素个数,sr[i]表示[l..i]中被分到右子结点的元素个数(这里l<=i<=r。显然sl[i]+sr[i]=i-l+1,其实sr[i]可以不用记录的,这里只是为了在找第K小操作中减少计算次数,起到空间换时间的作用)。
建树代码:
int  mkt( int  l,  int  r,  int  d)
{
    T[
++ No].l  =  l; T[No].r  =  r;  int  mid  =  l  +  r  >>   1 ; T[No].mid  =  mid;
    
if  (l  ==  r)  return  No;
    
int  midv  =  a0[mid], sm  =  mid  -  l  +   1 ; rre2(i, mid, l)  if  (a0[i]  <  midv) {sm  =  mid  -  i;  break ;}
    
int  x  =  l, y  =  mid  +   1 ;
    
if  (a[d][l]  <  midv) {
        a[d 
+   1 ][x ++ =  a[d][l]; sl[d][l]  =   1 ; sr[d][l]  =   0 ;
    } 
else   if  (a[d][l]  ==  midv  &&  sm) {
        a[d 
+   1 ][x ++ =  a[d][l]; sl[d][l]  =   1 ; sr[d][l]  =   0 ; sm -- ;
    } 
else  {
        a[d 
+   1 ][y ++ =  a[d][l]; sl[d][l]  =   0 ; sr[d][l]  =   1 ;
    }
    re3(i, l
+ 1 , r) {
        
if  (a[d][i]  <  midv) {
            a[d 
+   1 ][x ++ =  a[d][i]; sl[d][i]  =  sl[d][i  -   1 +   1 ; sr[d][i]  =  sr[d][i  -   1 ];
        } 
else   if  (a[d][i]  ==  midv  &&  sm) {
            a[d 
+   1 ][x ++ =  a[d][i]; sl[d][i]  =  sl[d][i  -   1 +   1 ; sr[d][i]  =  sr[d][i  -   1 ]; sm -- ;
        } 
else  {
            a[d 
+   1 ][y ++ =  a[d][i]; sl[d][i]  =  sl[d][i  -   1 ]; sr[d][i]  =  sr[d][i  -   1 +   1 ;
        }
    }
    
int  No0  =  No; T[No0].lch  =  mkt(l, mid, d  +   1 ); T[No0].rch  =  mkt(mid  +   1 , r, d  +   1 );  return  No0;
}
这里a是每层划分后的序列。
查找区间第K小的代码:
int  Find_Kth( int  l,  int  r,  int  K)
{
    
int  i  =  root, d  =   0 , l0, r0, mid0, s0, s1;
    
while  ( 1 ) {
        l0 
=  T[i].l, r0  =  T[i].r; mid0  =  T[i].mid;
        
if  (l0  ==  r0)  return  a[d][l];
        
if  (l  ==  l0) s0  =  l;  else  s0  =  l0  +  sl[d][l  -   1 ]; s1  =  l0  +  sl[d][r]  -   1 ;
        
if  (K  <=  s1  -  s0  +   1 ) {
            i 
=  T[i].lch; l  =  s0; r  =  s1; d ++ ;
        } 
else  {
            K 
-=  (s1  -  s0  +   1 );  if  (l  ==  l0) l  =  mid0  +   1 else  l  =  mid0  +  sr[d][l  -   1 +   1 ;
            r 
=  mid0  +  sr[d][r]; i  =  T[i].rch; d ++ ;
        }
    }
}

【具体题目】 PKU2104、 PKU2761(两道任选一道)

你可能感兴趣的:(划分树)