第k元素log(n)算法--划分树
来源:互联网 发布:java特种兵作者 编辑:程序博客网 时间:2024/05/19 08:24
第k元素log(n)算法--划分树
前几天学线段树,这个经典的K-th number一直没有做,关键是听别人说复杂度是log(n)^3,我对这个需要两次二分+一次查找的算法非常的不爽,于是一直拖着没搞
今天正准备着手这题的时候,发现PKU的Disscuss有人提到log(n)的算法,而且编程复杂度比log(n)^3的还小,于是对这种算法充满了憧憬,那个log(n)^3的写到一半也放弃了(其实log(n)^3的归并树算法化简了之后就是求n个有序数列的第k大数)
YY了很久之后,得到下边这个代码..关键部分已经很明白的加了的注释
完全看明白之后会发现一个非常有趣的现象,划分树逆着做就变成了归并树
(其实我也不知道这是不是hyerty大神所说的划分树,乱YY的)
画了一颗划分树对数列[1 5 2 3 6 4 7 3 0 0]进行划分,下图有助于理解(红色表示该数被分到左儿子)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
#define M 100001struct Seg_Tree{ int left,right; int mid() { return (left + right) >> 1; }}tt[M*4];int len;int sorted[M];int toLeft[20][M];int val[20][M]; void build(int l,int r,int d,int idx) { tt[idx].left = l; tt[idx].right = r; if(tt[idx].left == tt[idx].right) return ; int mid = tt[idx].mid(); int lsame = mid - l + 1;//lsame表示和val_mid相等且分到左边的 for(int i = l ; i <= r ; i ++) { if(val[d][i] < sorted[mid]) { lsame --;//先假设左边的数(mid - l + 1)个都等于val_mid,然后把实际上小于val_mid的减去 } } int lpos = l; int rpos = mid+1; int same = 0; for(int i = l ; i <= r ; i ++) { if(i == l) { toLeft[d][i] = 0;//toLeft[i]表示[ tt[idx].left , i ]区域里有多少个数分到左边 } else { toLeft[d][i] = toLeft[d][i-1]; } if(val[d][i] < sorted[mid]) { toLeft[d][i] ++; val[d+1][lpos++] = val[d][i]; } else if(val[d][i] > sorted[mid]) { val[d+1][rpos++] = val[d][i]; } else { if(same < lsame) {//有lsame的数是分到左边的 same ++; toLeft[d][i] ++; val[d+1][lpos++] = val[d][i]; } else { val[d+1][rpos++] = val[d][i]; } } } build(l,mid,d+1,LL(idx)); build(mid+1,r,d+1,RR(idx));} int query(int l,int r,int k,int d,int idx) { if(l == r) { return val[d][l]; } int s;//s表示[ l , r ]有多少个分到左边 int ss;//ss表示 [tt[idx].left , l-1 ]有多少个分到左边 if(l == tt[idx].left) { s = toLeft[d][r]; ss = 0; } else { s = toLeft[d][r] - toLeft[d][l-1]; ss = toLeft[d][l-1]; } if(s >= k) {//有多于k个分到左边,显然去左儿子区间找第k个 int newl = tt[idx].left + ss; int newr = tt[idx].left + ss + s - 1;//计算出新的映射区间 return query(newl,newr,k,d+1,LL(idx)); } else { int mid = tt[idx].mid(); int bb = l - tt[idx].left - ss;//bb表示 [tt[idx].left , l-1 ]有多少个分到右边 int b = r - l + 1 - s;//b表示 [l , r]有多少个分到右边 int newl = mid + bb + 1; int newr = mid + bb + b; return query(newl,newr,k-s,d+1,RR(idx)); }} int main() { int T; scanf("%d",&T); while(T --) { int n , m; scanf("%d%d",&n,&m); FOR(i,1,n+1) { scanf("%d",&val[0][i]); sorted[i] = val[0][i]; } sort(sorted + 1 , sorted + n + 1); build(1,n,0,1); while(m --) { int l,r,k; scanf("%d%d%d",&l,&r,&k); printf("%d\n",query(l,r,k,0,1)); } } return 0;}
后记:写完后去PKU交了一下,原以为不是rank1也至少是前十,结果连1s都没跑进去...
看了数据后发现m<=5000很少
意味着 nlogn(建树常数较大) + mlngn
和 nlogn(建树常数小)+mlogn^3
前者没占多少优势...
在我们hduoj也找了一道,所幸这题m <= 100000很大
两题比较:
OJ nlog(n) + mlog(n) nlog(n) + mlog(n)^3
HDU 少于500MS 3000MS左右
PKU 1000MS左右 1000-2000MS
2010.7.23跟新
扩展:
Minimum Sum
找到区间中的中位数,然后确定绝对值只和
就是找区间[l,r]的第(l-r+2)/2个数,而求和的话在Query函数里找到kth number后递归上来后再处理一下,需要另开一个数组sum[deep][i]表示第deep层,区间[ tt[idx].Left , i]的和
我比较懒都没有解释什么,只写了个代码
这篇文章解释的很清楚,可以去看看
我的代码只是为了理解方便点写的这么麻烦,其实划分树可以写的很简洁很简洁的,有好多地方可以优化~~
- 第k元素log(n)算法--划分树
- 第k元素 划分树
- n的k划分算法
- 寻找第K小元素O(N)算法
- Java实现O(log(n+m))两个有序数组中第K大元素或中位数
- 已知数组A[1...n] ,确定第K小元素 算法的时间复杂度O(n)
- 分治算法 求第k小元素 O(n) < O(nlog2^n)
- 从n个元素中选取第k大的元素,设计一个算法并说明算法复杂度
- 数组中查找第k小元素的复杂度为O(n)的算法
- K:找寻数组中第n大的数组元素的值的三个算法
- 分治算法;随机化划分函数;快速排序;线性时间选择第K小元素;快速排序平均时间复杂度nlgn;
- N个元素数组中第K大元素
- pku2104 第k大数-划分树做法
- 区间第K大(划分树)
- 划分树 hdu2665 第k小
- 算法求第K小元素思路
- 算法:寻找第K小元素
- BFPRT算法查找第k大元素
- android 存储图片到data目录和读取data目录下的图片
- 随机数生成解析
- 常见几个有关存储的名词,如硬盘、内存、u盘Mp3存储设备的特点及原因
- C常见问题之字符串数组和字符指针数组问题
- String 、StringBuffer 和 StringBuilder 的区别
- 第k元素log(n)算法--划分树
- 用C语言实现有限状态自动机FSM
- Linux硬件检测工具
- 登陆页优化的七大规则
- GPU编解码 - 硬解码---CUVID
- C语言实现命令行窗口
- hibernate映射继承关系(一):一张表对应一整棵类继承树
- c# - catch(Exception ex) 会丢掉StackTrace 是怎么回事?
- eas bos 编辑界面 editUIt 属性值为空