主席树。。高大上的名字。。原名叫可持久化线段树。。也有人叫函数式线段树(其实叫什么都不重要)。
本来的作用就是字面意思。。持久化的线段树,支持修改之后查找某次修改之前的版本。(在NOIP之前在算法导论上看到过,当时觉得没什么,现在才知道好厉害的数据结构)
具体来怎么实现呢。。其实就是每次修改的时候都新开一个根节点然后把和修改有关的区间一路新建下去,与修改无关的区间就继承上次修改版本的节点,这样会节省空间。
来应用一下,一个基础的应用就是区间K小值查询(poj2104)。即给定一个序列,给出L,R,K,求【L,R】中从小到大第K大的值是多少。
在这之前,先记下权值线段树,接着之前的意思举个例子,就是把序列里的数字按权值建立线段树(注意不是按位置),即权值线段树节点的[l,r]是权值,而w(count)为在[l,r]区间出现的权值的个数(而按位置建立线段树[l,r]是位置,w是在该位置上出现的权值)(其实权值线段树也可以拿来统计逆序对,这里不赘述)
回到原题,权值线段树和区间K小值有什么关系呢。。考虑对原区间每一个位置到区间开头的一段建一颗权值线段树,这样r位置的线段树某个节点(即[L,R])的权值减去l位置线段树对应节点(也是[L,R])的权值就是位置在(l,r]区间,权值在[L,R]之间的数的个数(之所以这样会成立,是因为这种统计满足区间加法和减法),也就方便查询了。于是,在插入位置x的权值时,将x-1位置的权值线段树当作一个历史版本,根据这个历史版本和x位置的权值建立一颗新版本的权值线段树,并保存下这棵树的根节点。这样这道题与权值线段树和主席树的联系就很明了了。查询时从l-1位置和r位置的根节点同时走下去(为表述方便,简称 l-1位置的线段树的对应节点为L节点,r为R节点),每到一个节点就询问差d=R节点的左子节点的count - L节点的左子节点的count,如果k<d,就同时往左走,否则就往右走并且k-=d,直到走到某个叶节点,这个叶节点代表的权值就是原来询问的区间K小值。
关于之前提到的区间加法:某性质满足区间加法,指区间的这种性质完全由子区间决定,如区间权值之和,区间最大值。
因为实际权值的范围可能非常大,权值线段树可能装不下或者有很大的空间浪费,所以,主席树经常与离散化搭配食用。
其实主席树在建立时是在做某种前缀和,而查询就是在做差分。类比一下差分的基本内容,就会发现主席树的另一种用法就是把每一个位置当作对前面的做前缀和,做区间修改时就修改l位置和r+1位置(CQOI2015任务查询系统),每次查询的时候在某个位置把这个版本的主席树当作完整的线段树查询。而待修改的区间K小值就可以用树状数组来维护(就像正常用树状数组维护区间和,做单点修改一样的道理)。
这里贴一下最基本的主席树的板子,值得注意的是主席树的空间要开到要维护区间的几十倍大小,板子里默认节点数组的0号位置代表空树。
关于更多持久化数据结构,参见《可持久化数据结构研究》 陈立杰
1 #include<stdio.h> 2 #define maxn 100 ///正常使用要开40倍左右 3 int tot; 4 struct node{int ch[2],cont,l,r;};//主席树节点 5 node seg[maxn];//主席树本体 6 int root[maxn];//每棵主席树的根节点 7 void ins(int now,int pre,int w) 8 { 9 seg[now].cont = seg[pre].cont + 1; 10 if(seg[now].l==seg[now].r) return; 11 int mid = (seg[now].l+seg[now].r)>>1; 12 if(w>mid) 13 { 14 seg[now].ch[0] = seg[pre].ch[0]; 15 seg[now].ch[1] = ++tot; 16 seg[tot].l = mid+1; seg[tot].r = seg[now].r; 17 ins(tot,seg[pre].ch[1],w); 18 } 19 else 20 { 21 seg[now].ch[1] = seg[pre].ch[1]; 22 seg[now].ch[0] = ++tot; 23 seg[tot].l = seg[now].l; seg[tot].r = mid; 24 ins(tot,seg[pre].ch[0],w); 25 } 26 } 27 int que(int now,int pre,int k) 28 { 29 int det; 30 while(1) 31 { 32 if(seg[now].l==seg[now].r) return seg[now].l; 33 det = seg[seg[now].ch[0]].cont-seg[seg[pre].ch[0]].cont; 34 if(det<k){now=seg[now].ch[1];pre=seg[pre].ch[1];k-=det;} 35 else{now=seg[now].ch[0];pre=seg[pre].ch[0];} 36 } 37 }
一些主席树有关的题目:poj2104 K-th Number,CQOI2015任务查询系统,SPOJ count on a tree,SCOI2016美味。果然自己还是做题少。
话说今天做HEOI2016序列的时候为什么会YY出主席树的做法