归并树学习笔记

归并树是这个题中官方题解用的做法。在做这个题之前我对这个数据结构一无所知,后来上网查阅了一些资料,然后写了这篇博客。尽管我到现在还没有用归并树过去这个题

前置知识

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];
}
posted @ 2023-12-01 16:38  lhc0707  阅读(116)  评论(0)    收藏  举报