据说最近挺热门的,想起寒假学过归并树,结果现在什么都不记得了,模拟了下归并排序终于想起来了
归并树
以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。于是我就找什么是唯一的,很容易想到数组的下标,仔细观察,如果从划分树叶子回溯,相当于在对数组下标进行归并排序,于是我的问题就解决了~
思考:划分树可以求逆序数吗,划分树中如何求一个数在某个区间的rank
归并树
以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。于是我就找什么是唯一的,很容易想到数组的下标,仔细观察,如果从划分树叶子回溯,相当于在对数组下标进行归并排序,于是我的问题就解决了~
思考:划分树可以求逆序数吗,划分树中如何求一个数在某个区间的rank
POJ 2104 寻找区间第K数
划分树,时间复杂度O(MlogN),归并树,时间复杂度O(Mlog^3N)。
归并树
- #include <queue>
- #include <stack>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <iostream>
- #include <limits.h>
- #include <string.h>
- #include <string>
- #include <algorithm>
- #define MID(x,y) ( ( x + y ) >> 1 )
- #define L(x) ( x << 1 )
- #define R(x) ( x << 1 | 1 )
- #define BUG puts("here!!!")
- using namespace std;
- const int MAX = 100010;
- class Merger_tree{
- public :
- class Tnode{
- public :
- int l,r;
- int len() { return r - l;}
- int mid() { return MID(l,r);}
- bool in(int ll,int rr) { return l >= ll && r <= rr; }
- void lr(int ll,int rr){ l = ll; r = rr;}
- };
- Tnode node[MAX<<2];
- int seg[20][MAX],a[MAX],n;
- void init()
- {
- memset(seg,0,sizeof(seg));
- memset(node,0,sizeof(node));
- }
- void build(int s,int t){ n = t; Merger_build(1,s,t,1); }
- int find(int x,int y,int k) { return find_rank(n,x,y,k); };
- void Merger_build(int t,int l,int r,int deep)
- {
- node[t].lr(l, r);
- if( node[t].len() == 0 )
- {
- seg[deep][l] = a[l];
- return ;
- }
- int mid = MID(l, r);
- Merger_build(L(t), l, mid, deep+1);
- Merger_build(R(t), mid+1, r, deep+1);
- int k = l,i = l,j = mid+1;
- while( i <= mid && j <= r )
- if( seg[deep+1][i] < seg[deep+1][j] )
- seg[deep][k++] = seg[deep+1][i++];
- else
- seg[deep][k++] = seg[deep+1][j++];
- while( i <= mid )
- seg[deep][k++] = seg[deep+1][i++];
- while( j <= r )
- seg[deep][k++] = seg[deep+1][j++];
- }
- int find_k(int t,int l,int r,int deep,int val)
- {
- if( node[t].in(l,r) )
- {
- int ll = node[t].l, rr = node[t].r;
- while( ll < rr )
- {
- int mid = MID(ll, rr);
- if( seg[deep][mid] < val )
- ll = mid + 1;
- else
- rr = mid;
- }
- if( seg[deep][ll] <= val )
- return ll - node[t].l + 1;
- else
- return ll - node[t].l;
- }
- if( node[t].len() == 0 ) return 0;
- int ans = 0;
- int mid = node[t].mid();
- if( l <= mid ) ans += find_k(L(t), l, r, deep+1, val);
- if( r >= mid ) ans += find_k(R(t), l, r, deep+1, val);
- return ans;
- }
- int find_rank(int n,int x,int y,int k)
- {
- int l = 1,r = n;
- while( l < r )
- {
- int mid = MID(l, r);
- if( find_k(1, x, y, 1, seg[1][mid]) < k )
- l = mid + 1;
- else
- r = mid;
- }
- return seg[1][l];
- }
- };
- Merger_tree t;
- int main()
- {
- int n,m,x,y,k;
- while( ~scanf("%d%d",&n,&m) )
- {
- t.init();
- for(int i=1; i<=n; i++)
- scanf("%d",&t.a[i]);
- t.build(1,n);
- while( m-- )
- {
- scanf("%d%d%d",&x,&y,&k);
- int ans = t.find(x,y,k);
- printf("%d\n",ans);
- }
- }
- return 0;
- }
划分树
- #include <queue>
- #include <stack>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <iostream>
- #include <limits.h>
- #include <string.h>
- #include <string>
- #include <algorithm>
- #define MID(x,y) ( ( x + y ) >> 1 )
- #define L(x) ( x << 1 )
- #define R(x) ( x << 1 | 1 )
- #define BUG puts("here!!!")
- using namespace std;
- const int MAX = 100010;
- class Parti_tree{
- public :
- class Tnode{
- public :
- int l,r;
- int len() { return r - l;}
- int mid() { return MID(l,r);}
- bool in(int ll,int rr) { return l >= ll && r <= rr; }
- void lr(int ll,int rr){ l = ll; r = rr;}
- };
- Tnode node[MAX<<2];
- int Left[20][MAX], seg[20][MAX], sa[MAX];
- void init()
- {
- memset(Left,0,sizeof(Left));
- memset(node,0,sizeof(node));
- }
- void build(int s,int t){ sort(sa+1,sa+t+1); Parti_build(1,s,t,1); }
- int find(int s,int t,int k){ return find_rank(1,s,t,1,k); }
- void Parti_build(int t,int l,int r,int d)
- {
- node[t].lr(l, r);
- if( node[t].len() == 0 ) return ;
- int mid = MID(l, r), lsame = mid - l + 1;
- for(int i=l; i<=r; i++)
- if( seg[d][i] < sa[mid] )
- lsame--;
- int lpos = l,rpos = mid+1,same = 0;
- for(int i=l; i<=r; i++)
- {
- if( i == l )
- Left[d][i] = 0;
- else
- Left[d][i] = Left[d][i-1];
- if( seg[d][i] < sa[mid] )
- {
- Left[d][i]++;
- seg[d+1][lpos++] = seg[d][i];
- }
- if( seg[d][i] > sa[mid] )
- seg[d+1][rpos++] = seg[d][i];
- if( seg[d][i] == sa[mid] )
- if( same < lsame )
- {
- same++;
- Left[d][i]++;
- seg[d+1][lpos++] = seg[d][i];
- }
- else
- seg[d+1][rpos++] = seg[d][i];
- }
- Parti_build(L(t), l, mid, d+1);
- Parti_build(R(t), mid+1, r, d+1);
- }
- int find_rank(int t,int l,int r,int d,int val)
- {
- if( node[t].len() == 0 )
- return seg[d][l];
- int s,ss;
- if( l == node[t].l )
- {
- s = Left[d][r];
- ss = 0;
- }
- else
- {
- s = Left[d][r] - Left[d][l-1];
- ss = Left[d][l-1];
- }
- if( s >= val )
- return find_rank(L(t), node[t].l+ss, node[t].l+ss+s-1, d+1, val);
- else
- {
- int mid = node[t].mid();
- int bb = l - node[t].l - ss;
- int b = r - l + 1 - s;
- return find_rank(R(t), mid+bb+1, mid+bb+b,d+1,val-s);
- }
- }
- };
- Parti_tree t;
- int main()
- {
- int n,m,x,y,k;
- while( ~scanf("%d%d",&n,&m) )
- {
- t.init();
- for(int i=1; i<=n; i++)
- {
- scanf("%d",&t.sa[i]);
- t.seg[1][i] = t.sa[i];
- }
- t.build(1,n);
- while( m-- )
- {
- scanf("%d%d%d",&x,&y,&k);
- int ans = t.find(x, y, k);
- printf("%d\n",ans);
- }
- }
- return 0;
- }
线段树一维的刷差不多了,求区间第K数一直卡着。
划分树和归并树都可以求,比较了一下时间效率,划分树比归并树快了很多,而且POJ有个求区间第K数的题用归并树居然过不去。
鉴于时间短,我决定把划分树给弄明白= =。。借用下小HH的图。
划分树和归并树都是用线段树作为辅助的,原理是基于 快排 和 归并排序 的。
划分树的建树过程基本就是模拟快排过程,取一个已经排过序的区间中值,然后把小于中值的点放左边,大于的放右边。并且记录d层第i个数之前(包括i)小于中值的放在左边的数。具体看下面代码注释。
关键是询问过程,具体见图。优快云现在上传图片质量好差啊啊啊啊啊啊 。
hdu3473是求中位数的(可以证得,差值和最小的一定是中位数(如果是偶数个的话,中间两个任意以一个均可)),但是涉及求和,涉及求得在区间[l,r]中,中位数之前元素的和(排好序后)。这样的话,在建树过程增加一个二维数组,第d层下标<=i之前元素的和。然后找的时候,求区间差值和即可。
附,poj2104代码,买一赠一,poj2761和2104基本一样
- #include <queue>
- #include <stack>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <iostream>
- #include <limits.h>
- #include <string.h>
- #include <string>
- #include <algorithm>
- #define MID(x,y) ( ( x + y ) >> 1 )
- #define L(x) ( x << 1 )
- #define R(x) ( x << 1 | 1 )
- #define BUG puts("here!!!")
- #define LL long long
- using namespace std;
- const int MAX = 100010;
- LL s[MAX];
- class Parti_tree{
- public :
- class Tnode{ // 我的一维线段树定义
- public :
- int l,r;
- int len() { return r - l;}
- int mid() { return MID(l,r);}
- bool in(int ll,int rr) { return l >= ll && r <= rr; }
- void lr(int ll,int rr){ l = ll; r = rr;}
- };
- Tnode node[MAX<<2];
- int num_left[20][MAX], seg[20][MAX],sa[MAX];//sa数组存sort后的结果
- //seg数组存的是d层划分后的数字 (类似快排Partation (d-1)次后的结果)
- //num_left存的是d层在i之前(包括i)小于 sa[mid] 的数的数目
- void init()
- {
- memset(seg,0,sizeof(seg));
- memset(num_left,0,sizeof(num_left));
- memset(node,0,sizeof(node));
- }
- void build(int s,int t)
- {
- sort(sa+s,sa+t+s);
- Parti_build(1,s,t,1);
- }
- int query(int s,int t,int k)
- {
- return find_rank(1,s,t,1,k);
- }
- void Parti_build(int t,int l,int r,int d)
- {
- node[t].lr(l, r);
- if( node[t].len() == 0 ) return ;
- int mid = MID(l, r), lsame = mid - l + 1;
- for(int i=l; i<=r; i++)//首先确定分到每一侧的数的数目
- if( seg[d][i] < sa[mid] )//因为相同的元素可能被分到两侧
- lsame--;
- int lpos = l,rpos = mid+1;
- for(int i=l; i<=r; i++)
- {
- if( i == l )
- num_left[d][i] = 0;
- else
- num_left[d][i] = num_left[d][i-1];
- if( seg[d][i] < sa[mid] )
- {
- num_left[d][i]++;
- seg[d+1][lpos++] = seg[d][i];
- }
- if( seg[d][i] > sa[mid] )
- seg[d+1][rpos++] = seg[d][i];
- if( seg[d][i] == sa[mid] )
- if( lsame > 0 ) // 如果大于0,说明左侧可以分和sa[mid]相同的数字
- {
- lsame--;
- num_left[d][i]++;
- seg[d+1][lpos++] = seg[d][i];
- }
- else //反之,说明左侧数字已经分满了,就分到右边去
- seg[d+1][rpos++] = seg[d][i];
- }
- Parti_build(L(t), l, mid, d+1);
- Parti_build(R(t), mid+1, r, d+1);
- }
- int find_rank(int t,int l,int r,int d,int val)
- {
- if( node[t].len() == 0 ) return seg[d][l];
- int s,ss; //s表示区间[l,r]有多少个小于sa[mid]的数被分到左边
- if( l == node[t].l )
- ss = 0;
- else
- ss = num_left[d][l-1];
- s = num_left[d][r] - ss;//ss表示从当前区间的L到l-1有多少个小于sa[mid]的数被分到左边
- if( s >= val )
- return find_rank(L(t), node[t].l+ss, node[t].l+ss+s-1, d+1, val);
- else
- {
- int mid = node[t].mid();
- int bb = l - node[t].l - ss; //表示从当前区间L到l-1有多少个分到右边
- int b = r - l + 1 - s; //表示[l,r]有多少个分到右边
- return find_rank(R(t), mid+bb+1, mid+bb+b,d+1,val-s);
- }
- }
- };
- Parti_tree t;
- int main()
- {
- int n,m,x,y,k;
- while( ~scanf("%d%d",&n,&m) )
- {
- t.init();
- for(int i=1; i<=n; i++)
- {
- scanf("%d",&t.sa[i]);
- t.seg[1][i] = t.sa[i];
- }
- t.build(1,n);
- while( m-- )
- {
- scanf("%d%d%d",&x,&y,&k);
- int ans = t.query(x, y, k);
- printf("%d\n",ans);
- }
- }
- return 0;
- }