划分树 图文讲解让你一次就懂 hdu2665为例
来源:互联网 发布:他改变了中国淘宝评论 编辑:程序博客网 时间:2024/05/16 08:07
学主席树的时候看到一篇博客说是因为一个大牛当时不会划分树而想出的另一种解决区间第k小的问题,所以在学了主席树之后我就学了下划分树。
划分树解决静态区间第k小问题比主席树的时空消耗都要少,不过好像不能解决动态区间第k小问题。
转载自:http://www.cnblogs.com/hchlqlz-oj-mrj/p/5744308.html
这篇博客写的非常好,所以我直接转载了。
划分树
划分树,类似线段树,主要用于求解某个区间的第k 大元素(时间复杂度log(n))。
下面以hdu2665为例进行讲解,给你n 个数的原序列,有m 次询问,每次询问给出l、r、k,求原序列l 到r 之间第k小的数。(n,m<=100000)
划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。见下图:
红点标记的就是进入左孩子的元素。
当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开val[20][N]来保存,也就是说共20层,每一层N个数。
我们还需要一个辅助数组num,num[i]表示i 前面有多少数进入左孩子(i 和i 前面可以弄成本结点内也可以是所有,两种风格不同而已,下面采取的是本结点内),和val一样,num也开成num[20][N],来表示每一层,i 和i 前面(本结点)有多少进入左孩子。
第一层:1 进入左孩子,num[1]=1,5 进入右孩子,num[2]=1,…,num[8]=4。
第二层:5 进入左孩子,num[5]=1,6 进入右孩子,num[6]=1,…,num[8]=2。
建图时就是维护每一层val[]和num[]的值就可以了。
int h[maxn]; //排序后数组int val[20][maxn]; //20层,每一层元素排放,0层就是原数组int num[20][maxn]; //num[i] 表示i前面有多少个点进入左孩子void build(int l,int r,int deep){ if(l==r) return; //cnt保存有多少和h[mid]一样大的数进入左孩子,用于保证最多只有一半的数进入左孩子 int mid = (l+r)>>1,cnt = mid-l+1; for(int i=l;i<=r;i++) if(val[deep][i]<h[mid]) cnt--; int ln=l,rn=mid+1; //本结点两个孩子结点的开头,ln左 for(int i=l;i<=r;i++) { if(i==l) num[deep][i] = 0; else num[deep][i] = num[deep][i-1]; if(val[deep][i]<h[mid] || val[deep][i]==h[mid]&&cnt>0) { val[deep+1][ln++] = val[deep][i]; num[deep][i]++; if(val[deep][i]==h[mid]) cnt--; } else { val[deep+1][rn++] = val[deep][i]; } } build(l,mid,deep+1); build(mid+1,r,deep+1);}
查询时,比如要查找2 到6 之间第3 大的数,那么先判断2 到6 之间有多少元素进入左子树,(在此忽略细节)num[6]-num[2-1]=2,就说明2 到6 有两个数进入左子树,又因为我们要找的是第3 大的数,所以一定在右子树中。可以算出,下标2 前面有0 个数进入右子树,所以2 到6 之间进入右子树的元素在下一层一定是从5 开始排的,2 到6 的区间进入右子树3 个,所以下一层从5 排到7 都是原本2 到6 之间的。现在,因为去左子树两个,所以现在要在右子树找5 到7 之间排名第1 的元素。在下一层的查找步骤和第一层一样,也就是不断递归,当跑到叶子结点时就可以返回正确的值了。
int query(int deep,int s,int e,int l,int r,int k){ if(l==r) return val[deep][l]; int pre; //pre表示s前面有多少元素进入左孩子 if(l==s) pre=0; //这里要特判一下和左端点重合时 else pre = num[deep][s-1]; //这一层s到e之间进入左子树的有cnt个 int mid = (l+r)>>1,cnt = num[deep][e]-pre; if(k<=cnt) { return query(deep+1,l+pre,l+num[deep][e]-1,l,mid,k); } else { // s-l 表示s前面有多少数,再减pre 表示这些数中去右子树的有多少个 int rn = mid+1+s-l-pre; // e-s+1 表示s到e有多少数,减去去左边的,剩下是去右边的,去右边1个,下标就是rn,所以减1 return query(deep+1,rn,rn+e-s+1-cnt-1,mid+1,r,k-cnt); }}
hdu2665
#include <bits/stdc++.h>using namespace std;typedef long long ll;const int maxn = 1e5+5;int n,m;int h[maxn],val[20][maxn],num[20][maxn];void build(int l,int r,int deep){ if(l==r) return; //cnt保存有多少和h[mid]一样大的数进入左孩子,用于保证最多只有一半的数进入左孩子 int mid = (l+r)>>1,cnt = mid-l+1; for(int i=l;i<=r;i++) if(val[deep][i]<h[mid]) cnt--; int ln=l,rn=mid+1; //本结点两个孩子结点的开头,ln左 for(int i=l;i<=r;i++) { if(i==l) num[deep][i] = 0; else num[deep][i] = num[deep][i-1]; if(val[deep][i]<h[mid] || val[deep][i]==h[mid]&&cnt>0) { val[deep+1][ln++] = val[deep][i]; num[deep][i]++; if(val[deep][i]==h[mid]) cnt--; } else { val[deep+1][rn++] = val[deep][i]; } } build(l,mid,deep+1); build(mid+1,r,deep+1);}int query(int deep,int s,int e,int l,int r,int k){ if(l==r) return val[deep][l]; int pre; //pre表示s前面有多少元素进入左孩子 if(l==s) pre=0; //这里要特判一下和左端点重合时 else pre = num[deep][s-1]; //这一层s到e之间进入左子树的有cnt个 int mid = (l+r)>>1,cnt = num[deep][e]-pre; if(k<=cnt) { return query(deep+1,l+pre,l+num[deep][e]-1,l,mid,k); } else { // s-l 表示s前面有多少数,再减pre 表示这些数中去右子树的有多少个 int rn = mid+1+s-l-pre; // e-s+1 表示s到e有多少数,减去去左边的,剩下是去右边的,去右边1个,下标就是rn,所以减1 return query(deep+1,rn,rn+e-s+1-cnt-1,mid+1,r,k-cnt); }}int main(){ int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&val[0][i]),h[i]=val[0][i]; sort(h+1,h+1+n); build(1,n,0); int l,r,k; while(m--) { scanf("%d%d%d",&l,&r,&k); printf("%d\n",query(0,l,r,1,n,k)); } } return 0;}
看一下和主席树的对比
划分树AC
主席树AC
- 划分树 图文讲解让你一次就懂 hdu2665为例
- 主席树(静态) 图文讲解让你一次就懂 hdu2665为例
- 主席树 (动态)图文讲解让你一次就懂 zoj2112为例
- HDU2665之划分树
- hdu2665(划分树)
- poj2104 hdu2665 划分树
- hdu2665 划分树
- 最详细的讲解,让你一次学会主席树
- poj2104||hdu2665 归并树|划分树
- HDU2665--Kth Number(划分树)
- 划分树 hdu2665 第k小
- poj 2104 or poj2761 or hdu2665 划分树
- HDU2665 Kth number(划分树模板题)
- 划分树讲解
- 让你用一次就会爱上的 5 个 web 工具类产品
- 同样的面试题,怎样回答可以让面试官一次就记住你!
- 最形象的讲解,让你一次学会什么叫LCA离线算法tarjan
- ac自动机最详细的讲解,让你一次学会ac自动机。
- 管理SQL Server 2008
- 基于JavaSwing+MySQL写的图书进销存管理系统
- Codeforces Round #432(Div. 1)
- qwt编译配置使用
- 600. Non-negative Integers without Consecutive Ones
- 划分树 图文讲解让你一次就懂 hdu2665为例
- 629. K Inverse Pairs Array
- iOS 镜像,旋转
- 物理引擎的空间数据结构
- AC自动机题集
- windows下安装scrapy
- 老妪能解PCA
- 565. Array Nesting
- UVA.1639 Candy (期望 高精度)