可持久化线段树/主席树(静态)

参考博客c++

先介绍一下主席树,主席树也称函数式线段树也称可持久化线段树。(其实就是支持查询历史版本,这个在看完以后就会了解)编程

其实主席树就是不少线段树组合的整体,从它的其它称呼也能够看出来了,其实它本质上仍是线段树。数组

主席树就是利用函数式编程的思想来使线段树支持询问历史版本、同时充分利用它们之间的共同数据来减小时间和空间消耗的加强版的线段树。那么它是怎么实现的呢?函数式编程

好比有4个数5 3 6 9,求区间[2,4]第2小的数。函数

T[i]表示第i棵线段树的根节点编号,L[i]表示节点i的左子节点编号,R[i]表示节点i的右子节点编号,sum[i]表示节点i对应区间中数的个数。
咱们先把序列离散化后是2 1 3 4。
我以前已经说了,主席树就是不少线段树的整体,而这些线段树就是按给定序列的全部前缀创建的。从T[0]开始创建空树,以后依次加入第i个数创建T[i]。
注意,若是咱们直接以序列的全部前缀创建线段树确定会MLE,这里主席树最精妙的地方就出来了。咱们创建的这些线段树的结构,维护的区间是相同的,主席树充分利用了这些线段树中的相同部分,大大减小了空间消耗,达到优化目的。优化

直接上图,边看图边理解上面的话。ui

 

图中上面为用序列全部前缀创建的线段树,下面为全部线段树组合成主席树。
图中每一个节点上面为节点编号,节点下面为对应区间,节点中数为区间中含有的数的个数,后面省略了区间。
从图中应该能够看出主席树是怎么充分利用这些线段树的相同结构来减小空间消耗的。当要新建一个线段树时最多只须要新增log2nlog2n个节点,至关于只更新了一条链,其它节点与它的前一个线段树公用。spa

建完主席树后咱们看看它是怎么查找区间[2,4]第2小的数的。
首先咱们要了解这些线段树是可加减的,好比咱们要处理区间[l,r],那么咱们只需处理sum[T[r]]-sum[T[l-1]]就是给定序列的区间[l,r]中的数的个数。由于咱们是按前缀处理的,这里看图本身体会一下。
这里咱们要先计算res=sum[L[T[4]]]-sum[L[T[1]]]=1,即算出给定序列区间[2,4]中数的范围在区间[1,2]的数的个数,若是它的值大于k那么咱们就应该从线段树的根节点走到左节点找第k个数,不然咱们就应该从根节点到右节点找第k-res个数,以后递归下去直到叶子节点,返回叶子节点对应区间即为咱们查找的数在离散化后序列中的下标。这里返回值为3,对应离散化后序列中数3,即原序列中数6。.net

讲到这里,静态的主席树就讲完了。咱们算算时空复杂度。
设原序列有n个数,含有m次询问
空间复杂度:(建空树)4*n+(前缀和更新)nlog2nlog2n
通常咱们数组大小就开nlog2nlog2n (动态不同,以后会讲)
时间复杂度:mlog2ncode

给一道裸题: 【模板】可持久化线段树 1(主席树)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn = 2e6+5;
 5 ll tree[maxn],L[maxn<<2],R[maxn<<2],sum[maxn<<2];
 6 ll sz[maxn],h[maxn];
 7 ll n,m,al,ar,tot,k;
 8 int buf[17];
 9 inline void read(ll &x){
10     char ch=getchar(); x=0;
11     while(ch<'0') ch=getchar();
12     while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
13 }
14 inline void write(int x){
15     if(!x){putchar('0');putchar(' ');return;}
16     register int cnt=0;
17     while(x)buf[++cnt]=(x%10)+48,x/=10;
18     while(cnt)putchar(buf[cnt--]);
19     putchar('\n');
20 }
21 inline void build(ll &root,ll l,ll r){
22     root = ++tot;
23     sum[root] = 0;
24     if(l == r)return;
25     ll mid = l+r>>1;
26     build(L[root],l,mid);
27     build(R[root],mid+1,r);
28 }
29 inline void update(ll &root,ll l,ll r,ll pre,ll x){
30     root = ++tot;
31     L[root] = L[pre];
32     R[root] = R[pre];
33     sum[root] = sum[pre]+1;
34     if(l == r)return;
35     ll mid =l+r>>1;
36     if(x <= mid)update(L[root],l,mid,L[pre],x);
37     else update(R[root],mid+1,r,R[pre],x);
38 }
39 inline ll query(ll s,ll e,ll l,ll r,ll k){
40     if(l == r)return l;
41     ll mid = l+r>>1;
42     int res = sum[L[e]]-sum[L[s]];
43     if(k <= res)return query(L[s],L[e],l,mid,k);
44     else return query(R[s],R[e],mid+1,r,k-res);
45 }
46 int main(){
47     read(n);
48     read(m);
49     for(register int i = 1;i <= n;i++)read(sz[i]),h[i] = sz[i];
50     sort(h+1,h+1+n);
51     int num = unique(h+1,h+1+n)-h-1;
52     tot = 0;
53     build(tree[0],1,num);
54     for(register int i = 1;i <= n;i++)
55         update(tree[i],1,num,tree[i-1],lower_bound(h+1,h+1+num,sz[i])-h);
56     while(m--){
57         read(al);
58         read(ar);
59         read(k);
60         int ans = query(tree[al-1],tree[ar],1,num,k);
61         write(h[ans]);
62     }
63     return 0;
64 }
相关文章
相关标签/搜索