「主席树」学习笔记

主席树

主席树——可持久化线段树。话说这个名字的来历也非常有意思,传说是一位非常非常巨的巨佬考场上现场yy出来了这种数据结构,他的名字叫做黄嘉泰(hjt)。于是就叫主席树了……

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

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

实现

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

(i)次更新后的线段树的根节点编号。记为(T[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)

传送门:>Here<

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

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

注意,权值线段树中叶子节点([x,x])的值即代表数字(x)出现的次数。于是主席树中的某个节点的值代表当前状态下某个区间内数字出现次数的总和

好了,现在考虑如何查询区间。我们发现要求第(K)大,实际上就是要求出每个区间内数字的个数。即区间([L,R])内某个数的出现次数,等于([1,R])内的出现次数减去([1,L-1])内的出现次数。因此在主席树上看来就是两个对应状态的线段树某个节点的值相减。相减之后我们就得到了这段区间内每个数的出现次数。然后就利用刚才的方法查询第(k)大就可以了

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100010;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
struct Num{
    int val,idx,rnk;
}a[MAXN];
int N,M,z,y,x;
int T[MAXN],mp[MAXN];
struct zxs{
    int l[MAXN<<5], r[MAXN<<5], amt[MAXN<<5], numNode;
    int update(int pre, int L, int R, int x){
        int cur = ++numNode;
        l[cur] = l[pre], r[cur] = r[pre], amt[cur] = amt[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;
    }
    int query(int pre, int u, int L, int R, int k){
        int sum = amt[l[u]] - amt[l[pre]];
        if(L == R) return mp[L];
        if(sum >= k) return query(l[pre], l[u], L, (L+R)/2, k);
        else return query(r[pre], r[u], (L+R)/2+1, R, k-sum);
    }
}hjt;
inline bool cmp1(const Num& a, const Num& b){ return a.val < b.val; }
inline bool cmp2(const Num& a, const Num& b){ return a.idx < b.idx; }
int main(){
//  freopen(".in","r",stdin);
    N = read(), M = read();
    for(int i = 1; i <= N; ++i){
        a[i].val = read();
        a[i].idx = i;
    }
    sort(a+1, a+N+1, cmp1);
    for(int i = 1; i <= N; ++i){
        if(i != 1 && a[i].val == a[i-1].val) a[i].rnk = a[i-1].rnk;
        else a[i].rnk = i;
    }
    sort(a+1, a+N+1, cmp2);
    for(int i = 1; i <= N; ++i){
        T[i] = hjt.update(T[i-1], 1, N, a[i].rnk);
        mp[a[i].rnk] = a[i].val;
    }
    for(int i = 1; i <= M; ++i){
        x = read(), y = read(), z = read();
        printf("%d
", hjt.query(T[x-1], T[y], 1, N, z));
    }
    return 0;
}

静态区间第(K)小 经典应用一:树上问题

[ 应用 1 ]: >题解:洛谷P2633 (Count on a tree)<

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

原文地址:https://www.cnblogs.com/qixingzhi/p/9692308.html