可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。
因此可持久化线段树也叫函数式线段树又叫主席树。
可持久化数据结构
在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本。这样的集合称为是可持久的。
实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间。
考虑一个持久集合S。
如图所示,对集合的每一个版本维护一个单独的根,在修改数据时,只复制树的一部分。
称之为可持久化数据结构。
可持久化线段树
令 T 表示一个结点,它的左儿子是 left(T),右儿子是 right(T)。
若 T 的范围是 [L,R],那么 left(T) 的范围是 [L,mid],right(T) 的范围是 [mid+1,R]。
单点更新
我们要修改一个叶子结点的值,并且不能影响旧版本的结构。
在从根结点递归向下寻找目标结点时,将路径上经过的结点都复制一份。
找到目标结点后,我们新建一个新的叶子结点,使它的值为修改后的版本,并将它的地址返回。
对于一个非叶子结点,它至多只有一个子结点会被修改,那么我们对将要被修改的子结点调用修改函数,那么就得到了它修改后的儿子。
在每一步都向上返回当前结点的地址,使父结点能够接收到修改后的子结点。
在这个过程中,只有对新建的结点的操作,没有对旧版本的数据进行修改。
区间查询
从要查询的版本的根节点开始,像查询普通的线段树那样查询即可。
延迟标记
...
区间第K小值问题
有n个数,多次询问一个区间[L,R]中第k小的值是多少。
查询[1,n]中的第K小值
我们先对数据进行离散化,然后按值域建立线段树,线段树中维护某个值域中的元素个数。
在线段树的每个结点上用cnt记录这一个值域中的元素个数。
那么要寻找第K小值,从根结点开始处理,若左儿子中表示的元素个数大于等于K,那么我们递归的处理左儿子,寻找左儿子中第K小的数;
若左儿子中的元素个数小于K,那么第K小的数在右儿子中,我们寻找右儿子中第K-(左儿子中的元素数)小的数。
查询区间[L,R]中的第K小值
我们按照从1到n的顺序依次将数据插入可持久化的线段树中,将会得到n+1个版本的线段树(包括初始化的版本),将其编号为0~n。
可以发现所有版本的线段树都拥有相同的结构,它们同一个位置上的结点的含义都相同。
考虑第i个版本的线段树的结点P,P中储存的值表示[1,i]这个区间中,P结点的值域中所含的元素个数;
假设我们知道了[1,R]区间中P结点的值域中所含的元素个数,也知道[1,L-1]区间中P结点的值域中所包含的元素个数,显然用第一个个数减去第二个个数,就可以得到[L,R]区间中的元素个数。
因此我们对于一个查询[L,R],同步考虑两个根root[L-1]与root[R],用它们同一个位置的结点的差值就表示了区间[L,R]中的元素个数,利用这个性质,从两个根节点,向左右儿子中递归的查找第K小数即可。
POJ 2104 K-th Number (HDU 2665)
注意可持久化数据结构的内存开销非常大,因此要注意尽可能的减少不必要的空间开支。
1 const int maxn=100001; 2 struct Node{ 3 int ls,rs; 4 int cnt; 5 }tr[maxn*20]; 6 int cur,rt[maxn]; 7 void init(){ 8 cur=0; 9 } 10 inline void push_up(int o){ 11 tr[o].cnt=tr[tr[o].ls].cnt+tr[tr[o].rs].cnt; 12 } 13 int build(int l,int r){ 14 int k=cur++; 15 if (l==r) { 16 tr[k].cnt=0; 17 return k; 18 } 19 int mid=(l+r)>>1; 20 tr[k].ls=build(l,mid); 21 tr[k].rs=build(mid+1,r); 22 push_up(k); 23 return k; 24 } 25 int update(int o,int l,int r,int pos,int val){ 26 int k=cur++; 27 tr[k]=tr[o]; 28 if (l==pos&&r==pos){ 29 tr[k].cnt+=val; 30 return k; 31 } 32 int mid=(l+r)>>1; 33 if (pos<=mid) tr[k].ls=update(tr[o].ls,l,mid,pos,val); 34 else tr[k].rs=update(tr[o].rs,mid+1,r,pos,val); 35 push_up(k); 36 return k; 37 } 38 int query(int l,int r,int o,int v,int kth){ 39 if (l==r) return l; 40 int mid=(l+r)>>1; 41 int res=tr[tr[v].ls].cnt-tr[tr[o].ls].cnt; 42 if (kth<=res) return query(l,mid,tr[o].ls,tr[v].ls,kth); 43 else return query(mid+1,r,tr[o].rs,tr[v].rs,kth-res); 44 }