【ECNU OJ 2918】k-th number 可持久化线段树 + 离散化

什么叫可持久化线段树?

我看到非常一个犀利的解释:

“线段树是让你在区间内进行修改以及询问的一个数据结构,但可持久化线段树就是想考考你,询问第x个版本的线段树的结果”

 

先要明白我们不可能真的取造n个线段树,必然是分分钟TLEMLE。 而是要在原来的线段树上进行操作,才能达到目标。

 

说实话,其实构建方法很简单巧妙。

1、 首先要明确的一点是:由于可持久化线段树保存历史版本,不能像原先一样利用二进制的特点,位掩码来表示两个儿子。从而产生的一个很老实的想法就是开一个计数器,每有一个结点被建立,index++,当前树节点用index来表示。

2、 如果要更新单个点:只更新和其有关的树节点
这也是为什么这个构造方法省空间的原因。比如说你在[1,8]的线段树上更新<5>
step1:新建一个root节点,拷贝原root的数据。
step2:左子树不变,更新右子树,现在更新区域为[5,8]
step3:新建一个[5,8]节点,拷贝原数据
step4:右子树不变,更新[5,6]………… 以此类推

3、 如果更新区间:
标记永久化,以后会填。

 

这样的线段树是必须要用build函数了,毕竟没用树状结构支持了

 小技巧就是传引用,这样子新建节点起来就很方便

void Build(int &node, int left, int right)
{
    node = ++cnt;
    tr[node].left  = left;
    tr[node].right = right;
    
    if(left == right)return;
    
    int mid = (left + right) >> 1;
    Build(tr[node].lson,left,mid);
    Build(tr[node].rson,mid + 1,right);
}


然后考虑k-th number这道题

首先是个统计k大的题目可以让每个节点表示某个值出现了几次。可以利用lower_bound进行离散化。

然后依次插入每个数据,于是 root[i] 表示已经维护了 a1,a2,a3....ai 前缀的线段树。


然后是询问操作:
对于朴素的统计线段树,询问第k大很简单。
对于某个节点,如果lson的数字总数 > k 那么显然第k的数肯定在左子树区间里
                        如果lson的数字总数 < k 那么显然第k的树在右子树区间里,并且在右子树是排名第k - numbers[lson]的数


对于可持久化线段树:
由于root[n] 维护的是数列的前缀, 如果要查询区间第k。

把问题转换为:
在root[left-1]里没有,但在root[right]里有的区间第k数

如何实现?
每考虑一个区间,要考虑 cnt = root[r].numbers - root[l].numbers
表示区间内有没有 “root[r]包含但,root[l]不包含”的个数。

即在查询过程中,把与非持久线段树的数字总数比较 改成 可持久化线段树与cnt的比较  就可以完成了。

#include <bits/stdc++.h>
#define lson(node) tree[node].Lson
#define rson(node) tree[node].Rson
#define MAX_N 100010
using namespace std;
struct TreeNode{
	int Left, Right;
	int Lson, Rson;
	int Sum;
}tree[MAX_N *20];

int sortedArray[MAX_N];
int getposArray[MAX_N];
int root[MAX_N];
int Index = 0;

inline void copy_node(const int from , const int * const to){
	assert(to != NULL);
	tree[*to].Left  =  tree[from].Left;
	tree[*to].Right =  tree[from].Right;
	tree[*to].Lson  =  tree[from].Lson;
	tree[*to].Rson  =  tree[from].Rson;
	tree[*to].Sum   =  tree[from].Sum + 1;
}

void build( int& node, int left , int right ){
	node = ++Index;
	tree[node].Left  = left; 
	tree[node].Right = right;
	
	if( left == right ) return;
	
	int mid = ( left + right ) >> 1;
	build( lson(node) , left , mid );
	build( rson(node) , mid+1 , right );
}

void insert( int pre , int& node , const int& pos ){
	node = ++Index;
	copy_node(pre,&node);
	if( tree[node].Left == tree[node].Right ) return;
	
	int mid = ( tree[node].Left + tree[node].Right ) >> 1;
	if(pos <= mid)
		insert( tree[pre].Lson, tree[node].Lson, pos );
	else
		insert( tree[pre].Rson, tree[node].Rson, pos );
}

int Query( int pre , int node , int k ){
	if(tree[node].Left == tree[node].Right)
		return sortedArray[tree[node].Left];
	int cnt = tree[lson(node)].Sum - tree[lson(pre)].Sum;
	if(k <= cnt)
		return Query( tree[pre].Lson, tree[node].Lson, k );
	else
		return Query( tree[pre].Rson, tree[node].Rson, k-cnt ); 
}


int main(){
	int n = 0 , q = 0;
	scanf("%d %d",&n,&q);
	for (int i = 1 ; i <= n ; sortedArray[i] = getposArray[i] , ++i)
		scanf("%d",&getposArray[i]);
	sort(sortedArray+1,sortedArray+1+n);
	build(root[0],1,n);
	for (int i = 1 ; i <= n ; ++i)
	{
		int pos = lower_bound(sortedArray+1,sortedArray+1+n,getposArray[i]) - sortedArray;
		insert(root[i-1],root[i],pos);
	}

	int l = 0 , r = 0 , k = 0; 
	for (int i = 1 ; i <= q ; ++i)
	{
		scanf("%d %d %d",&l,&r,&k);
		printf("%d\n",Query(root[l-1],root[r],k));
	}
	return 0;
}

写完以后眉头一紧

root[r] , root[l-1] ,前缀

这不是树状数组维护的吗?

关于这个我下次找机会专门写。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值