什么叫可持久化线段树?
我看到非常一个犀利的解释:
“线段树是让你在区间内进行修改以及询问的一个数据结构,但可持久化线段树就是想考考你,询问第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] ,前缀
这不是树状数组维护的吗?
关于这个我下次找机会专门写。