zoukankan      html  css  js  c++  java
  • 可持久化线段树/主席树(静态)

    参考博客

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

    其实主席树就是很多线段树组合的总体,从它的其它称呼也可以看出来了,其实它本质上还是线段树。

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

    比如有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,这里主席树最精妙的地方就出来了。我们建立的这些线段树的结构,维护的区间是相同的,主席树充分利用了这些线段树中的相同部分,大大减少了空间消耗,达到优化目的。

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

     

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

    建完主席树后我们看看它是怎么查找区间[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。

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

    给一道裸题: 【模板】可持久化线段树 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('
    ');
    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 }
  • 相关阅读:
    oracle改表语句
    pr视频过渡效果
    远程桌面连接
    kill-power
    Leetcode 466.统计重复个数
    Leetcode 464.我能赢吗
    Leetcode 462.最少移动次数使数组元素相等
    Leetcode 459.重复的子字符串
    Leetcode 458.可怜的小猪
    Leetcode 457.环形数组循环
  • 原文地址:https://www.cnblogs.com/wangyifan124/p/10325352.html
Copyright © 2011-2022 走看看