「主席树」学习笔记

主席树

主席树——可持久化线段树。话说这个名字的来历也非常有意思,传说是一位非常非常巨的巨佬发明的,他的名字叫做黄嘉泰。由于名字缩写和一位竹席的缩写一模一样,于是就叫主席树了……

所谓可持久化线段树,就是可以查询历史更新信息的线段树。例如我对线段树进行了5次更新,但是需要查询第2次更新结束后的结果……一种显而易见的做法是每次更新重新建树。但是这样的空间不知爆到哪里去了……

其实主席树的做法也是比较天然的。考虑一次单点更新只会对线段树的一串节点(\(logn\)个)做出修改,剩下的节点依然没有改变。因此考虑每一次添加一批节点,使这批节点依附在原始的线段树上。

实现

在普通的线段树中,我们按照规则:左子树的编号为父亲2,右子树编号为父亲2+1. 然而这个规则在主席树里就不再适用了,因为为了充分利用空间,一个父亲可能对应多个儿子。因此我们需要专门记录每个节点的左右儿子编号

同时,由于根节点维护的是总体信息。因此每次更新时,根节点一定有所改变。所以我们可以每次记录一下,第\(i\)次更新后的线段树的更节点编号。记为\(T[i]\)。然而只要知道根节点编号,就能够一步一步找到其对应的线段树。一个根节点对应一个状态的线段树,不同的状态的线段树可能有公共节点。

在代码实现上,我们先假设其左右儿子不做更改,然后再一个一个改变下去。

int update(int pre, int L, int R, int x){
    //pre表示更新前那个状态的线段树,[L,R]表示当前节点维护的区间,x表示当前更新的值 
    int cur = ++numNode;
    l[cur] = l[pre], r[cur] = r[pre], sum[cur] = sum[pre] + 1;
    if(L < R){
        if(x <= (L+R)/2) l[cur] = update(l[pre], L, (L+R)/2, x);
        else r[cur] = update(r[pre], (L+R)/2+1, R, x);  
        //直接改变左右儿子的编号。注意一定只改一个 
    }
    return cur;//这里包括L==R的情况 
}

注意这里的\(update\)函数是有返回值的,返回更新成功后新的那一个节点的编号。

静态区间第\(K\)

采用可持久化权值线段树。

权值线段树查询总体第\(K\)小并不难,和平衡树查询第\(K\)小非常相似。先礼散花,然后利用权值线段树维护每个区间(叶子当然是从小到大的权值了)的数字个数。查询时只需要从根节点往下走,每次比较其左儿子出现个数是否大于或等于\(K\)。如果左儿子中的数字个数都已经\(\geq K\)了,往左儿子走即可。否则就往右儿子走,记得此时应当继续查询第\(K-sum[lson]\)

注意,权值线段树中叶子节点\([x,x]\)的值即代表数字\(x\)出现的次数

好了,现在考虑如何查询区间。我们发现要求第\(K\)大,实际上就是要求出每个区间内数字的个数。那么现在加入要求区间\([L,R]\)内的数字个数,即为\([1,R]-[1,L-1]\)的数字个数。我们可以对于原序列,一个一个数字插入主席树。从左往右,每次更新一个数字。这样的话,区间\([L,R]\)内出现的数字个数就等同于当前线段树的状态,去减去第\(L-1\)次更新时的线段树状态。这就要用到可持久化线段树了。

于是问题迎刃而解

剩余部分待更…………………………

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9692308.html