http://baike.baidu.com/view/4199603.htm
划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值。
http://yueyue1105.blog.163.com/blog/static/431117682010716111425892/
同样以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。于是我就找什么是唯一的,很容易想到数组的下标,仔细观察,如果从划分树叶子回溯,相当于在对数组下标进行归并排序,于是我的问题就解决了~
http://java-mans.iteye.com/blog/1644583
https://github.com/zsc1993916/AcmTemplate/blob/master/Datastructure/%E5%88%92%E5%88%86%E6%A0%91%E6%B1%82%E5%8C%BA%E9%97%B4k%E5%A4%A7%E6%95%B0.cpp
http://java-mans.iteye.com/blog/1644582
划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值。
http://yueyue1105.blog.163.com/blog/static/431117682010716111425892/
同样以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。于是我就找什么是唯一的,很容易想到数组的下标,仔细观察,如果从划分树叶子回溯,相当于在对数组下标进行归并排序,于是我的问题就解决了~
http://java-mans.iteye.com/blog/1644583
归并树是在建树的过程中保存归并排序。
划分树是在建树的过程中保存快速排序。
其中归并树适合解决一个数在某个区间的名次。
划分树适合解决某个区间的K大数。
POJ这题是找K大数,归并树也可做,二分答案,再由归并树找出这个数的名次。划分树更快。
- struct Node{
- int left,right,mid;
- }tree[N*4];
- int sa[N],num[20][N],cnt[20][N]; //sa中是排序后的,num记录每一层的排序结果,cnt[deep][i]表示第deep层,前i个数中有多少个进入左子树
- int n,q;
- void debug(int d){
- for(int i=1;i<=n;i++)
- printf("%d ",num[d][i]);
- printf("\n");
- }
- void bulid(int step,int l,int r,int deep){
- tree[step].left=l;
- tree[step].right=r;
- tree[step].mid=(l+r)>>1;
- if(l==r)
- return;
- int mid=(l+r)>>1;
- int mid_val=sa[mid],lsum=mid-l+1;;
- for(int i=l;i<=r;i++)
- if(num[deep][i]<mid_val)
- lsum--; //lsum表示左子树中还需要多少个中值
- int L=l,R=mid+1;
- for(int i=l;i<=r;i++){
- if(i==l)
- cnt[deep][i]=0;
- else
- cnt[deep][i]=cnt[deep][i-1];
- if(num[deep][i]<mid_val||(num[deep][i]==mid_val&&lsum>0)){ //左子树
- num[deep+1][L++]=num[deep][i];
- cnt[deep][i]++;
- if(num[deep][i]==mid_val)
- lsum--;
- }
- else
- num[deep+1][R++]=num[deep][i];
- }
- // debug(deep);
- bulid(2*step,l,mid,deep+1);
- bulid(2*step+1,mid+1,r,deep+1);
- }
- int query(int step,int l,int r,int k,int deep){
- if(l==r)
- return num[deep][l];
- int s1,s2; //s1为[tree[step].left,l-1]中分到左子树的个数
- if(tree[step].left==l)
- s1=0;
- else
- s1=cnt[deep][l-1];
- s2=cnt[deep][r]-s1; //s2为[l,r]中分到左子树的个数
- if(k<=s2) //左子树的数量大于k,递归左子树
- return query(2*step,tree[step].left+s1,tree[step].left+s1+s2-1,k,deep+1);
- int b1=l-1-tree[step].left+1-s1; //b1为[tree[step].left,l-1]中分到右子树的个数
- int b2=r-l+1-s2; //b2为[l,r]中分到右子树的个数
- return query(2*step+1,tree[step].mid+1+b1,tree[step].mid+1+b1+b2-1,k-s2,deep+1);
- }
- int main(){;
- while(scanf("%d%d",&n,&q)!=EOF){
- for(int i=1;i<=n;i++){
- scanf("%d",&num[1][i]);
- sa[i]=num[1][i];
- }
- sort(sa+1,sa+n+1);
- bulid(1,1,n,1);
- while(q--){
- int l,r,k;
- scanf("%d%d%d",&l,&r,&k);
- printf("%d\n",query(1,l,r,k,1));
- }
- }
- return 0;
- }
https://github.com/zsc1993916/AcmTemplate/blob/master/Datastructure/%E5%88%92%E5%88%86%E6%A0%91%E6%B1%82%E5%8C%BA%E9%97%B4k%E5%A4%A7%E6%95%B0.cpp
http://java-mans.iteye.com/blog/1644582