主席树摘要

介绍&主要用途

主席树,据说由fotile大佬因不会划分树而发明。由于大佬名字缩写为HJT而被Seter大佬命名为主席树。

主要用于查询区间第k值。

原理

首先离散化,对每个节点i建一棵[1,i]的权值线段树。这里用到了前缀和思想并且满足可加减性。但这会MLE。然后我们发现,对于每个点的线段树,它和前面那个节点的线段树有且仅有logn个节点是不同的,因此我们可以借用前一个节点的线段树,仅仅新建logn个节点。这个思想成为了主席树和所有可持久化数据结构的思想基础。

变量

1 int n,m;
2 int a[maxn];                                                                                //初始数组
3 int lch[maxn],all;                                                                            //离散化
4 struct Node {
5     int L,R,sum;                                                                            //左儿子、右儿子编号和在此区间内的数的个数
6 } tree[maxn*16];
7 int ins;
8 int root[maxn];                                                                                //每个节点的线段树的根
var

建树

整个序列直接建树有点困难(也可能是我太菜了),考虑依次插入每个数。

如果到叶子节点就在前一个点的基础上更新并直接返回,否则为相应的儿子分配新节点。

 1 void insert(int now,int l,int r,int x,int pla) {                                            //now为前一个点的当前编号,pla为分配节点编号
 2     if(l==r) {
 3         tree[pla].sum=tree[now].sum+1;                                                        //在前一个点的基础上更新节点
 4         return;
 5     }
 6     int mid=l+r>>1;
 7     if(x>mid) {
 8         tree[pla].L=tree[now].L;                                                            //复制左儿子
 9         insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins));                                    //新建右儿子并向右走
10     } else {
11         tree[pla].R=tree[now].R;                                                            //复制右儿子
12         insert(tree[now].L,l,mid,x,(tree[pla].L=++ins));                                    //新建左儿子并向左走
13     }
14     update(pla);
15 }
insert

查询区间[l,r]第k小

利用前缀和思想,传入l-1和r,相减即可。

1 int query(int l,int r,int z,int y,int k) {
2     if(l==r) return l;                                                                        //叶子节点就是答案
3     int mid=l+r>>1;
4     return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)?
5            query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)):    //左边太少,往右走
6            query(l,mid,tree[z].L,tree[y].L,k);                                                //左边太多,往左走
7 }
query

时空复杂度

时间复杂度

建树:O(logn)

单次查询:和线段树一样O(logn)

空间复杂度

由于每个值仅出现logn次,有n个值,因此空间复杂度为O(nlogn)

题目

洛谷P3834 【模板】可持久化线段树 1(主席树)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x7fffffff
 4 #define ME 0x7f
 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i])
 9 #define ll long long
10 #define MEM(a,b) memset(a,b,sizeof(a))
11 #define maxn (200000+10)
12 int n,m;
13 int a[maxn];
14 int lch[maxn],all;
15 struct Node{
16     int L,R,sum;
17 }tree[maxn*16];
18 int ins;
19 int root[maxn];
20 template<class T>
21 inline T read(T &n){
22     n=0;int t=1;double x=10;char ch;
23     for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0';
24     for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0';
25     if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10;
26     return (n*=t);
27 }
28 template<class T>
29 T write(T n){
30     if(n<0) putchar('-'),n=-n;
31     if(n>=10) write(n/10);putchar(n%10+'0');return n;
32 }
33 template<class T>
34 T writeln(T n){write(n);putchar('\n');return n;}
35 void update(int x){tree[x].sum=tree[tree[x].L].sum+tree[tree[x].R].sum;}
36 void insert(int now,int l,int r,int x,int pla){
37     if(l==r){tree[pla].sum=tree[now].sum+1;return;}int mid=l+r>>1;
38     if(x>mid){tree[pla].L=tree[now].L;insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins));}
39     else{tree[pla].R=tree[now].R;insert(tree[now].L,l,mid,x,(tree[pla].L=++ins));}update(pla);
40 }
41 int query(int l,int r,int z,int y,int k){
42     if(l==r) return l;int mid=l+r>>1;
43     return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)?
44         query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)):
45         query(l,mid,tree[z].L,tree[y].L,k);
46 }
47 int main(){
48     read(n);read(m);
49     fui(i,1,n,1) lch[i]=read(a[i]);
50     sort(lch+1,lch+n+1);all=unique(lch+1,lch+n+1)-lch-1;
51     fui(i,1,n,1){
52         int p=lower_bound(lch+1,lch+all+1,a[i])-lch;
53         insert(root[i-1],1,all,p,(root[i]=++ins));
54     }
55     fui(i,1,m,1){
56         int l,r,k;
57         read(l);read(r);read(k);
58         writeln(lch[query(1,all,root[l-1],root[r],k)]);
59     }
60     return 0;
61 }
AC代码

 

作者:A星际穿越
我的博客写得这么烂,应该不会有人想转载的吧
如果要转载的话,请在文章显眼处标明作者和出处谢谢
posted @ 2018-08-25 14:36  A星际穿越  阅读(168)  评论(0编辑  收藏  举报