poj2104 划分树 区间K大 在线 无修改
2014-08-03 23:26
357 查看
博主sbit。。。。对于高级数据结构深感无力,然后这些东西在OI竟然烂大街了,不搞就整个人都不好了呢。
于是我勇猛的跳进了这个大坑
——sbit
区间K大的裸题,在线,无修改。
可以用归并树(\(O(nlog^3n)\)),也可用划分树(\(O(nlogn + mlogn)\))。果断划分树。。。(以后再来看归并树。。。再来看。。。来看。。看。。)
划分树是个什么东西呢?为什么可以做区间k大呢?
想想平衡树做k大时是如何搞的,其实内在原理是一样的。
划分树分两个步骤:建树与询问。
1. 建树
划分树借鉴了快排的思想。划分树的每个节点保存了一个区间,以此区间为根节点,把区间分为左子树[left, mid]和右子树[mid + 1, right]的两个子树,保证左子树内的的值不大于根节点中中位数的值,右子树不小于之,且数值在子树中的顺序遵从在根节点中时的相对位置关系。关键之处在于,给每个数值记录一个to_left,表示从[left, i]中,被划分到左子树的值的数量,在查询中,这将起到至关的作用。对于没有相同取中位数值的元素时,只要比对大小关系来进行划分即可,但是,如果有相同取中位数值的元素时,如何处理这些元素呢?
method 1: 离散化。。简洁易懂,方便快捷。
method 2: 这个方法很巧妙,网上大多数代码(都是抄hh的,sbit也是的,羞耻play了)都使用了这个方法。参考资料1给出了详细的解释。
引用自参考资料1:
划分的时候还有一点需要处理:如果有多个数据相同怎么办呢?通过一种特殊的处理:尽量使左右两边平均分配相同的数。这个特殊处理是这样的:
在没分之前,先假设中位数左边的数据suppose都已经分到左边了,所以suppose=mid-left+1;然后如果真的分在左边,即if(tree[level][i]<sorted[mid])
suppose--;suppose就减一!到最后,如果suppos=1,则说明中位数左边的数都小于中位数,如果有等于中位数的,则suppose大于1。
最后分配的时候,把suppose个数,分到左边就可以了,剩下的分到右边!因为suppose的初值是mid-left+1,这样就能保证中位数左边和右边的数平衡了!
2. 询问
类似于平衡树求k大,利用上文求出来的to_left值,我们可以通过深入划分树的层级对k的值进行缩小,最后当区间长度等于1时,k等于1,答案只有一个——就是当前值啦!用纸画画就能明白了。
View Code
参考资料:
/article/4520655.html
http://blog.sina.com.cn/s/blog_5f5353cc0100ki2e.html
http://www.cppblog.com/MatoNo1/archive/2011/06/27/149604.html
http://www.xuebuyuan.com/829409.html
http://shizhixinghuo.diandian.com/post/2012-09-02/40037691896
http://baike.baidu.com/view/4199603.htm
http://barty.ws/partitiontree-%E5%88%92%E5%88%86%E6%A0%91/
于是我勇猛的跳进了这个大坑
——sbit
区间K大的裸题,在线,无修改。
可以用归并树(\(O(nlog^3n)\)),也可用划分树(\(O(nlogn + mlogn)\))。果断划分树。。。(以后再来看归并树。。。再来看。。。来看。。看。。)
划分树是个什么东西呢?为什么可以做区间k大呢?
想想平衡树做k大时是如何搞的,其实内在原理是一样的。
划分树分两个步骤:建树与询问。
1. 建树
划分树借鉴了快排的思想。划分树的每个节点保存了一个区间,以此区间为根节点,把区间分为左子树[left, mid]和右子树[mid + 1, right]的两个子树,保证左子树内的的值不大于根节点中中位数的值,右子树不小于之,且数值在子树中的顺序遵从在根节点中时的相对位置关系。关键之处在于,给每个数值记录一个to_left,表示从[left, i]中,被划分到左子树的值的数量,在查询中,这将起到至关的作用。对于没有相同取中位数值的元素时,只要比对大小关系来进行划分即可,但是,如果有相同取中位数值的元素时,如何处理这些元素呢?
method 1: 离散化。。简洁易懂,方便快捷。
method 2: 这个方法很巧妙,网上大多数代码(都是抄hh的,sbit也是的,羞耻play了)都使用了这个方法。参考资料1给出了详细的解释。
引用自参考资料1:
划分的时候还有一点需要处理:如果有多个数据相同怎么办呢?通过一种特殊的处理:尽量使左右两边平均分配相同的数。这个特殊处理是这样的:
在没分之前,先假设中位数左边的数据suppose都已经分到左边了,所以suppose=mid-left+1;然后如果真的分在左边,即if(tree[level][i]<sorted[mid])
suppose--;suppose就减一!到最后,如果suppos=1,则说明中位数左边的数都小于中位数,如果有等于中位数的,则suppose大于1。
最后分配的时候,把suppose个数,分到左边就可以了,剩下的分到右边!因为suppose的初值是mid-left+1,这样就能保证中位数左边和右边的数平衡了!
2. 询问
类似于平衡树求k大,利用上文求出来的to_left值,我们可以通过深入划分树的层级对k的值进行缩小,最后当区间长度等于1时,k等于1,答案只有一个——就是当前值啦!用纸画画就能明白了。
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 100000 + 10; int val[20][maxn], sorted[maxn], to_left[20][maxn]; int n, m; void build_tree(int l, int r, int layer) { if(l == r) return ; int mid = (l + r) >> 1; int suppose = mid - l + 1; for(int i = l; i <= r; ++i) if(val[layer][i] < sorted[mid]) --suppose; int rec_l = l, rec_r = mid + 1; for(int i = l; i <= r; ++i) { if(i == l) { to_left[layer][i] = 0; } else { to_left[layer][i] = to_left[layer][i - 1]; } if(val[layer][i] < sorted[mid]) { ++to_left[layer][i]; val[layer + 1][rec_l++] = val[layer][i]; } else if(val[layer][i] > sorted[mid]) { val[layer + 1][rec_r++] = val[layer][i]; } else { if(suppose != 0) { --suppose; ++to_left[layer][i]; val[layer + 1][rec_l++] = val[layer][i]; } else { val[layer + 1][rec_r++] = val[layer][i]; } } } build_tree(l, mid, layer + 1); build_tree(mid + 1, r, layer + 1); } int query(int l, int r, int layer, int ql, int qr, int kth) { if(l == r) return val[layer][l]; int s, ss; if(l == ql) { s = 0; ss = to_left[layer][qr]; } else { s = to_left[layer][ql - 1]; ss = to_left[layer][qr] - s; } int mid = (l + r) >> 1; if(kth <= ss) { return query(l, mid, layer + 1, l + s, l + s + ss - 1, kth); } return query(mid + 1, r, layer + 1, mid + 1 + ql - s - l, mid + 1 + qr - l - s - ss, kth - ss); } int main() { #ifndef ONLINE_JUDGE freopen("data.in", "r", stdin); freopen("data.out", "w", stdout); #endif scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++i) { scanf("%d", &sorted[i]); val[0][i] = sorted[i]; } sort(sorted + 1, sorted + n + 1); build_tree(1, n, 0); while(m--) { int l, r, k; scanf("%d%d%d", &l, &r, &k); printf("%d\n", query(1, n, 0, l, r, k)); } return 0; }
View Code
参考资料:
/article/4520655.html
http://blog.sina.com.cn/s/blog_5f5353cc0100ki2e.html
http://www.cppblog.com/MatoNo1/archive/2011/06/27/149604.html
http://www.xuebuyuan.com/829409.html
http://shizhixinghuo.diandian.com/post/2012-09-02/40037691896
http://baike.baidu.com/view/4199603.htm
http://barty.ws/partitiontree-%E5%88%92%E5%88%86%E6%A0%91/
相关文章推荐
- poj2104 主席树 区间K大 在线 无修改
- BZOJ 1901: Zju2112 Dynamic Rankings 区间k大 带修改 在线 线段树套平衡树
- BZOJ 3110([Zjoi2013]K大数查询-区间第k大[段修改,在线]-树状数组套函数式线段树)
- POJ2104:划分树求区间第k小
- 【poj2104-求区间第k大数(不修改)】主席树/可持续化线段树
- 划分树的用法(一):查询区间第K大值值(poj2104)
- BZOJ 3110([Zjoi2013]K大数查询-区间第k大[段修改,在线]-树状数组套函数式线段树)
- poj2104 求区间第k大数(划分&&主席--待补)
- BZOJ 1901(Zju2112 Dynamic Rankings-区间第k大(修改,在线)-函数式线段树)
- 【poj2104】不带修改的区间第k大 主席树
- 查询区间第k大 POJ2104 暴力 or 划分树 or 归并树
- 划分树的用法(一):查询区间第K大值值(poj2104)
- 划分树的用法(一):查询区间第K大值值(poj2104)
- 11g中_serial_direct_read是动态参数,可以在线修改
- 点修改区间查询 HDU1166
- eclipse myeclipse修改工作区间 an error has occurred. see error log for more details. java.lang.nullpointerexception 问题解决
- hdu 4902 Nice boat(线段树区间修改,输出最终序列)
- 树状数组 区间修改查询
- HDU - 1698 Just a Hook (线段树区间修改)
- HZAU 1207 Candies(线段树区间查询 区间修改)