一、理解
划分树,字面意思是将一列数按照一定规则划分出来形成一个树形的数据结构,而树形的数据结构特点就是查询快,时间复杂度在log级。
二、通俗定义
划分树就是将n个数按一定顺序,逐层划分,形成一个树状结构,但不改变每个数之间的相对位置。
例如:
将一列数按照从小到大的顺序划分,小的一半进入当前节点的左子节点,大的一半进入当前节点的右子节点,但在每个节点内每个数相对原序列的位置不变。
三、用途
主要用于快速求出(在log(n)的时间复杂度内)某个序列区间的第k大值。例如求出 l-r 区间内第k大的数。
四、算法思路
首先这个算法分为建树和查询两个部分。
建树部分,首先需要一个二维数组来存这个树。而每一层都是原本的n个数只是顺序不同而已。所以我们可以开一个数组tree[20][N],也就是说这个树有20层,每层都是原来的那n个数。(20层就足够多了)
同时我们还需要一个数组来记录每一个节点有多少个数进入左子节点,即我们定义一个数组为toleft[20][maxn],toleft[p][i]表示第p层本i之前节点内有多少个数进入左子节点。
然后依次每一层维护tree数组和toleft数组即可。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
const int MAXN = 100010;
int tree[20][MAXN];//表示每层每个位置的值
int sorted[MAXN];//已经排序好的数
int toleft[20][MAXN];//toleft[p][i] 表示第 i 层从 1 到 i 有数分入左边
void build(int l,int r,int dep) {
if(l == r)return;
int mid = (l+r)>>1;
int same=mid-l+1;//表示等于中间值而且被分入左边的个数
for(int i = l; i <= r; i++) //注意是 l, 不是 one
if(tree[dep][i] < sorted[mid])
same--;
int lpos = l;
int rpos = mid+1;
for(int i = l; i <= r; i++) {
if(tree[dep][i] < sorted[mid])
tree[dep+1][lpos++] = tree[dep][i];
else if(tree[dep][i] == sorted[mid] && same > 0) {
tree[dep+1][lpos++] = tree[dep][i];
same--;
} else
tree[dep+1][rpos++] = tree[dep][i];
toleft[dep][i]=toleft[dep][l-1]+lpos-l;
}
build(l,mid,dep+1);
build(mid+1,r,dep+1);
}
查找的部分是首先判断你要找的数在左子树还是右子树,如果在左子树,就在询问区间 l-r 和左子树区间的交集内找第k大的数,如果在右子树,就在询问区间 l-r 和右子树区间的交集内找第k-mid大的数,这是一个递归的过程知道找到数的叶子节点就找到了这个数。
//查询区间第 k 大的数,[L,R] 是大区间,[l,r] 是要查询的小区间
int query(int L,int R,int l,int r,int dep,int k) {
if(l == r)return tree[dep][l];
int mid = (L+R)>>1;
int cnt = toleft[dep][r] - toleft[dep][l-1];
if(cnt >= k) {
int newl=L+toleft[dep][l-1]-toleft[dep][L-1];
int newr=newl+cnt-1;
return query(L,mid,newl,newr,dep+1,k);
} else {
int newr=r+toleft[dep][R]-toleft[dep][r];
int newl=newr-(r-l-cnt);
return query(mid+1,R,newl,newr,dep+1,k-cnt);
}
}
int main() {
int n,m;
while(scanf("%d%d",&n,&m)==2) {
memset(tree,0,sizeof(tree));
for(int i = 1; i <= n; i++) {
scanf("%d",&tree[0][i]);
sorted[i] = tree[0][i];
}
sort(sorted+1,sorted+n+1);
build(1,n,0);
int s,t,k;
while(m--) {
scanf("%d%d%d",&s,&t,&k);
printf("%d\n",query(1,n,s,t,0,k));
}
}
return 0;
}
这两部分代码来自kuangbin的板子。
经典例题是POJ2104,这是一道板子题,直接套板子就可以AC。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=222;
const int MAXN = 100010;
int tree[20][MAXN];
int sorted[MAXN];
int toleft[20][MAXN];
void build(int l,int r,int dep) {
if(l==r)
return;
int mid=(l+r)>>1;
int same= mid-l+1;
for(int i=l; i<=r; i++) {
if(tree[dep][i]<sorted[mid]) {
same--;
}
}
int lpos=l;
int rpos=mid+1;
for(int i=l; i<=r; i++) {
if(tree[dep][i]<sorted[mid])
tree[dep+1][lpos++]=tree[dep][i];
else if(tree[dep][i]==sorted[mid]&&same>0) {
tree[dep+1][lpos++]=tree[dep][i];
same--;
} else
tree[dep+1][rpos++]=tree[dep][i];
toleft[dep][i]=toleft[dep][l-1]+lpos-l;
}
build(l,mid,dep+1);
build(mid+1,r,dep+1);
}
int query(int L,int R,int l,int r,int dep,int k) {
if(l==r) return tree[dep][l];
int mid=(L+R)>>1;
int cnt=toleft[dep][r]-toleft[dep][l-1];
if(cnt>=k) {
int newl=L+toleft[dep][l-1]-toleft[dep][L-1];
int newr=newl+cnt-1;
return query(L,mid,newl,newr,dep+1,k);
} else {
int newr=r+toleft[dep][R]-toleft[dep][r];
int newl=newr-(r-l-cnt);
return query(mid+1,R,newl,newr,dep+1,k-cnt);
}
}
int main() {
int n,m;
while(scanf("%d%d",&n,&m)==2) {
memset(tree,0,sizeof(tree));
for(int i=1; i<=n; i++) {
scanf("%d",&tree[0][i]);
sorted[i]=tree[0][i];
}
sort(sorted+1,sorted+1+n);
build(1,n,0);
int s,t,k;
while(m--) {
scanf("%d%d%d",&s,&t,&k);
printf("%d\n",query(1,n,s,t,0,k));
}
}
return 0;
}