简略介绍可持久化线段树

可持久化线段树是指保存了历史操作版本的线段树,就好比一个软件更新,它的历代版本都可以下载,这对于求解区间第K位的问题很有帮助。

因为可持久化线段树是把每个数据通过离散化的方式保存,而且在过程中保存的是插入不同的数后的整棵树的状态,而两个状态之间作差恰好就是某个区间的范围,再通过单点查询即可找到答案。

所谓离散化,就是以相对位置代替原数的值来存储这个数。比如成绩表,一个人对应一个成绩,同时也对应一个排名,我们既可以通过乘积找到这个人,也可以通过排名找到这个人,而这个排名就是相对大小。就比如说9 5 4 10这四个数中,5本身就是5的数值,可以直接索引到,而在这个区间里第2小也可以代表5,所以找第2小也是在找5。

类似于前缀和数组,建立可持久化线段树的第一步是建立一棵空树,相当于sum[0]=0,再将每个数逐一插入,并用一个数组记录下这棵树不同状态下的根节点是哪一个,凭此可以找到不同情况时树的状态。

当你要查询某个区间内第K位的数时,就需要找到这两个状态对应的树的根节点(某个数组专门存储),然后遍历每个节点时都要对当前节点的左子节点值作前缀和式的差来确定这个点保存的是在当前区间范围内的数据。为什么要对左子节点作差呢?这意味着你需要一个判定标准决定你是要遍历左子树还是右子树,这个差值就是判定标准——意味着这个区间内有多少数。如果左子树有k个数,那么你就可以继续往左遍历。如果根本没有k个数,你就得去遍历右子树。值得注意的是遍历右子树需要要把k减去这个差值,因为你刚刚放弃遍历的左子树里也包含一些数,虽然没有你需要的,但是他们的相对位置也不可以忽视。比如说1 2 3 4四个数,1 2 在左子树,3 4在右子树。如果你要找第3小(第2大)的数,左子树显然没有三个数,所以不能遍历,必须遍历右子树。而右子树的第1个元素并不是这个区间内的第1小的数,而是第n+1小的数(n为左子树元素个数)。

这就是可持久化线段树的模板式的基本操作。

//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
#define BigInteger __int128
using namespace std;
const int INF = 2e9 + 2;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
//	BigInteger tmp = 0, si = 1;char c;	c = getchar();
//	while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
//	while (isdigit(c))
//	{tmp = tmp * 10 + c - '0';c = getchar();}
//	return si * tmp;	
//}			
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
//    if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
//    return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
//	if (x < 0)
//	{x = -x;putchar('-');}
//	if (x > 9) output(x / 10);
//	putchar(x % 10 + '0');
//	}
/**Maintain your determination.Nobody knows the magnificent landscape
at his destination before the arrival with stumble.**/
/**Last Remote**/
struct node
{
	ll leftson, rightson;
}nodes[4591000];
ll cnt = 0;
ll sum[4591000],editions[4591000];//前缀和与保存根节点的数组
ll construction(ll left, ll right)//建树
{
	ll current = ++cnt;
	if (left < right)
	{
		ll mid = (left + right) >> 1;
		nodes[current].leftson = construction(left, mid);
		nodes[current].rightson = construction(mid + 1, right);
	}
	return current;
}
ll modification(ll PreviousEdition, ll left, ll right, ll location)
{
	ll current = ++cnt;
	nodes[current].leftson = nodes[PreviousEdition].leftson;
	nodes[current].rightson = nodes[PreviousEdition].rightson;
    //因为在插入数字的时候只会改变一小部分节点,所以那些没有改变的节点就可以共用
	sum[current] = sum[PreviousEdition] + 1;
	if (left < right)
	{
		ll mid = (left + right) >> 1;
		if (location <= mid)
			nodes[current].leftson = modification(nodes[PreviousEdition].leftson, left, mid, location);
		else
			nodes[current].rightson = modification(nodes[PreviousEdition].rightson, mid + 1, right, location);	
	}
	return current;
}
ll investigation(ll oldEdition, ll newEdition, ll left, ll right, ll kth)
{
	if (left == right)
		return left;
	else
	{
		ll frequency = sum[nodes[newEdition].leftson] - sum[nodes[oldEdition].leftson];
    //做差值
		ll mid = (left + right) >> 1;
		if (frequency >= kth)
			return investigation(nodes[oldEdition].leftson, nodes[newEdition].leftson, left, mid, kth);
		else
			return investigation(nodes[oldEdition].rightson, nodes[newEdition].rightson, mid + 1,right, kth-frequency);
	}
}
ll arr[591000], arrb[591000];
int DETERMINATION()
{
	//ios::sync_with_stdio(false);
	//cin.tie(0),cout.tie(0)
	ll n, m;
	lldin(n), lldin(m);
	for (int i = 1; i <= n; i++)
	{
		lldin(arr[i]);
		arrb[i] = arr[i];
	}
	cnt = 0;
	sort(arrb + 1, arrb + 1 + n);
	ll limit = unique(arrb + 1, arrb + n + 1) - arrb - 1;
    //排序去重后就是相对大小
	editions[0] = construction(1, limit);//保存根节点
	for (int i = 1; i <=n; i++)
	{
		ll tmp = lower_bound(arrb + 1, arrb + 1 + limit, arr[i]) - arrb;
    //在有序数组里找原数组某个元素的相对位置
		editions[i] = modification(editions[i - 1], 1, limit, tmp);
	}
	for (int i = 1; i <= m; i++)
	{
		ll lower, upper, kth;
		lldin(lower), lldin(upper), lldin(kth);
		println(arrb[investigation(editions[lower - 1], editions[upper], 1, limit, kth)]);
	}
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值