【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] ,前缀

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

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


ECNU计科考研复试机试是上海东华大学计算机科学与技术专业硕士研究生复试环节的一部分。机试一般包括计算机基础知识测试、编程实践、算法设计与分析、数据结构、数据库等内容。 机试的目的是通过实际操作和任务完成,评估考生的计算机基础知识、编程能力和解决问题的能力。机试一般会提供一些实际问题,考生需要根据题目要求进行编程实现,并实现功能要求以及考察的相关知识点。机试的题目会有一定的难度,需要考生具备扎实的计算机基础知识和编程实践经验。 针对ECNU计科考研复试机试的准备,考生可以从以下几个方面进行: 1. 夯实计算机基础知识:系统复习计算机组成原理、操作系统、数据结构、数据库等相关课程的基础知识点,理解并掌握核心概念和原理。 2. 学习编程技巧:熟练掌握至少一种编程语言,例如C++、Java等,并了解常用的编程工具和调试技巧,提高编程能力。 3. 解题经验积累:多做一些编程题和算法题,提高解题能力和编程实践经验。可以通过参加一些线上或线下的编程竞赛来提升自己的算法和编程水平。 4. 多做模拟机试:通过模拟机试,熟悉机试的形式和题目类型,提前感受机试的压力和难度,并对自己的不足进行总结和改进。 总之,ECNU计科考研复试机试是对考生计算机基础知识和编程实践能力的综合考查,需要考生充分准备和深入理解相关知识点。只有全面提高自己的计算机科学水平,才能在机试中取得好成绩。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值