现在的位置: 首页 > 综合 > 正文

归并树 hdu 2665 Kth number(线段树+归并树+二分)

2017年12月13日 ⁄ 综合 ⁄ 共 3051字 ⁄ 字号 评论关闭
 


归并树
以1 5 2 6 3 7为例:
把归并排序递归过程记录下来即是一棵归并树:
        [1 2 3 5 6 7]
    [1 2 5]      [3 6 7]
   [1 5] [2]    [6 3] [7] 
  [1][5]        [6][3]
用对应的下标区间建线段树:(这里下标区间对应的是原数列)
            [1 6]
     [1 3]      [4 6]
  [1 2] [3]   [4 5][6]
  [1][2]      [4][5]
每次查找[l r]区间的第k大数时,在[1 2 3 4 5 6 7]这个有序的序列中二分所要找的数x,然后对应到线段树中去找[l r]中比x小的数有几个,即x的rank。由于线段树中任意区间对应到归并树中是有序的,所以在线段树中的某个区间查找比x小的数的个数也可以用二分在对应的归并树中找。这样一次查询的时间复杂度是log(n)^2。
要注意的是,多个x有相同的rank时,应该取最大的一个。

划分树
同样以1 5 2 6 3 7为例:
根据中位数mid,将区间划分成左子树中的数小于等于mid,右子树中的数大于等于mid,得到这样一棵划分树:
        [1 5 2 6 3 7]
     [1 2 3]      [5 6 7]
   [1 2]  [3]    [5 6] [7]
  [1] [2]        [5] [6] 
注意要保持下标的先后顺序不变
对每一个区间,用sum[i]记录区间的左端点left到i有几个进入了左子树,即有几个数小于等于mid
用对应的下标区间建线段树:(这里下标区间对应的是排序后的数列)
            [1 6]
     [1 3]      [4 6]
  [1 2] [3]   [4 5][6]
  [1][2]      [4][5]
每次查找[l r]区间的第k大数时,先查看当前区间[left right]下的sum[r] - sum[l - 1]是否小于等于k,如果是,则递归到左子树,并继续在[left + sum[l - 1], left + sum[r] - 1]中找第k大数;
否则,进入右子树,继续在[mid + l - left + 1 - sum[l - 1], mid + r - left + 1 - sum[r]]找第k - sum[r] + sum[l - 1]大数
这样一次查询只要logn的复杂度

对于建划分树的方法,我一开始是先建完一层,再往下递归,过了PKU2104后,交HDU2665WA,后来发现对于0 0 -1这样的数据,下一层本应该是-1 0 0,而我的程序还是0 0 -1,原因就是会有很多相同的元素等于mid。于是我就找什么是唯一的,很容易想到数组的下标,仔细观察,如果从划分树叶子回溯,相当于在对数组下标进行归并排序,于是我的问题就解决了~

思考:划分树可以求逆序数吗,划分树中如何求一个数在某个区间的ran


hdu 2665 Kth number(线段树+归并树+二分)

分类: ACM水题之路—线段树 293人阅读 评论(0) 收藏 举报
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #include <iostream>  
  4. #include <algorithm>  
  5. using namespace std;  
  6. #define maxn 100005  
  7. #define maxd 21  
  8. struct seg{  
  9.     int l,r,m;  
  10. }tr[maxn<<2];  
  11. int s[maxn],segt[maxd][maxn<<2],n;  
  12. void build(int l,int r,int rt,int d){  
  13.     tr[rt].l=l;  
  14.     tr[rt].r=r;  
  15.     tr[rt].m=(l+r)>>1;  
  16.     int m=tr[rt].m;  
  17.     if(l==r){  
  18.         segt[d][l]=s[l];  
  19.         return ;  
  20.     }  
  21.   
  22.     build(l,m,rt<<1,d+1);  
  23.     build(m+1,r,rt<<1|1,d+1);  
  24.   
  25.     int i=l,j=m+1,k=l;  
  26.     while(i<=m && j<=r){//数值的归并  
  27.         if(segt[d+1][i]>segt[d+1][j])  
  28.             segt[d][k++]=segt[d+1][j++];  
  29.         else  
  30.             segt[d][k++]=segt[d+1][i++];  
  31.     }  
  32.     while(i<=m)  
  33.         segt[d][k++]=segt[d+1][i++];  
  34.     while(j<=r)  
  35.         segt[d][k++]=segt[d+1][j++];  
  36.   
  37. }  
  38. int countsig(int l,int r,int rt,int key,int d){//找出区间比其小的个数  
  39.     return lower_bound(segt[d]+l,segt[d]+r+1,key)-(segt[d]+l);  
  40. }  
  41. int count(int l,int r,int rt,int key,int d){  
  42.     if(tr[rt].l == l && tr[rt].r == r){  
  43.         return countsig(l,r,rt,key,d);  
  44.     }  
  45.     int m=tr[rt].m;  
  46.     if(r<=m)  
  47.         return count(l,r,rt<<1,key,d+1);  
  48.     else if(l>m)  
  49.         return count(l,r,rt<<1|1,key,d+1);  
  50.     else  
  51.         return count(l,m,rt<<1,key,d+1)+count(m+1,r,rt<<1|1,key,d+1);  
  52. }  
  53. int query(int L,int R,int cnt){  
  54.     int l=1,r=n;  
  55.     while(l<=r){  
  56.         int m=(l+r)>>1;  
  57.         int t=count(L,R,1,segt[1][m],1);  
  58.         if(t>=cnt)  
  59.             r=m-1;  
  60.         else  
  61.             l=m+1;  
  62.     }  
  63.     return segt[1][l-1];  
  64. }  
  65. int main(){  
  66.     int m;  
  67.     while(~scanf("%d %d",&n,&m)){  
  68.         for(int i=1;i<=n;i++)  
  69.             scanf("%d",&s[i]);  
  70.         build(1,n,1,1);  
  71.         while(m--){  
  72.             int l,r,cnt;  
  73.             scanf("%d %d %d",&l,&r,&cnt);  
  74.             printf("%d\n",query(l,r,cnt));  
  75.         }  
  76.     }  
  77.     return 0;  
  78. }
     

抱歉!评论已关闭.