归并树学习笔记
归并树是这个题中官方题解用的做法。在做这个题之前我对这个数据结构一无所知,后来上网查阅了一些资料,然后写了这篇博客。尽管我到现在还没有用归并树过去这个题
前置知识
1.归并排序
2.线段树
请在阅读以下内容前确保你已经熟练掌握这两个知识。
Part 1:是什么、为什么
归并树其实就是将归并排序的每一步都记录在一颗树里,每一层代表归并排序到这一步时序列的状态。
归并树的作用其实也很明显了。归并树的节点维护了区间上的有序,那么我们就可以利用归并树进行区间上的二分查找,当然,归并树也可以解决区间第 \(k\) 大的问题。
Part 2:怎么写
1.存储
可以知道归并树每一层大小都是数列长度,深度为
\(\log_2 N\) ,那么我们就可以用这样一个二维数组来存。
如果题目对空间卡的比较死,可以用vector。后文中,我将使用静态数组的方式存储归并树。
2.建树
类似线段树,用归并排序的方法就可以了。
void build(int dep,int pl,int pr)
{
if(pl==pr){tree[dep][pl]=a[pl];return;}
int mid=(pl+pr)>>1;
build(dep+1,pl,mid);build(dep+1,mid+1,pr);
int i=pl,j=mid+1,k=pl;
while(i<=mid||j<=pr)
{
if(j>pr)tree[dep][k++]=tree[dep+1][i++];
else if(i>mid||tree[dep+1][i]>tree[dep+1][j])tree[dep][k++]=tree[dep+1][j++];
else tree[dep][k++]=tree[dep+1][i++];
}
return;
}
3.各种操作
(1) 查询区间内比 x 小的数的个数
类似线段树,一旦有树上区间被完全包围,就在这个树上区间内二分查找,非常好写。
int calc(int deep,int pl,int pr,int l,int r,int x)
{
if(l<=pl&&pr<=r)
return lower_bound(tree[deep]+pl,tree[deep]+pr+1,x)-tree[deep]-pl;
int mid=(pl+pr)>>1,ret=0;
if(l<=mid)ret+=calc(deep+1,pl,mid,l,r,x);
if(r>mid)ret+=calc(deep+1,mid+1,pr,l,r,x);
return ret;
}
(2) 区间第 k 大数
有了 calc 操作,区间第 k 大数也变得好些起来。可以知道归并树第一层的数据是排好序的,决策有单调性,可以用二分查找,看区间 \([ l , r ]\) 中小于等于在 mid 位置上的数的数有多少个即可。
int kth_num(int l,int r,int k)
{
int i=1,j=n,mid;
while(i<=j)
{
mid=(i+j)>>1;
if(calc(1,1,n,l,r,tree[1][mid])<=k)i=mid+1;
else j=mid;
}
return tree[1][i-1];
}

浙公网安备 33010602011771号