主席树(静态) 图文讲解让你一次就懂 hdu2665为例

版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77871278

主席树学了几天才算初步了解了,我先讲一下无修改即静态的主席树,之后在讲带修改即动态的主席树。主席树比较抽象,感觉很难没有图只靠文字理解。
接下来进入正题

参考博客:http://www.cnblogs.com/zyf0163/p/4749042.html

主席树

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

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

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

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

直接上图,边看图边理解上面的话。
这里写图片描述

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

建完主席树后我们看看它是怎么查找区间[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+(前缀和更新)n log2n
一般我们数组大小就开n log2n (动态不一样,之后会讲)
时间复杂度:m log2n

hdu2665

题意
别被它的题目描述骗了,这题其实就是主席树求静态区间第k小的裸题。序列n范围<=1e5,询问m范围<=1e5。

题解
是裸题,直接按上面讲的写即可,具体可看代码注释。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 1e5+5;
int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20];
//sz[]为原序列,h[]为离散化后序列
int sz[maxn],h[maxn];
int n,q,ql,qr,k,tot;

void build(int& rt,int l,int r) //建空树
{
    rt = ++tot;
    sum[rt] = 0;
    if(l==r) return;
    int mid = (l+r)>>1;
    build(L[rt],l,mid);
    build(R[rt],mid+1,r);
}

//对所有前缀更新树
void update(int& rt,int l,int r,int pre,int x)
{
    rt = ++tot;
    L[rt]=L[pre];
    R[rt]=R[pre];
    sum[rt] = sum[pre]+1;
    if(l==r) return;
    int mid = (l+r)>>1;
    if(x<=mid) update(L[rt],l,mid,L[pre],x);
    else update(R[rt],mid+1,r,R[pre],x);
}

int query(int s,int e,int l,int r,int k)
{
    if(l==r) return l;
    int mid = (l+r)>>1;
    int res = sum[L[e]]-sum[L[s]]; //左子节点数的个数
    if(k<=res) return query(L[s],L[e],l,mid,k);
    else return query(R[s],R[e],mid+1,r,k-res);
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++) scanf("%d",sz+i),h[i]=sz[i];
        sort(h+1,h+1+n);
        int num = unique(h+1,h+1+n)-h-1;
        tot=0;
        build(T[0],1,num);
        for(int i=1;i<=n;i++)
        {
            //离散化后更新
            update(T[i],1,num,T[i-1],lower_bound(h+1,h+1+num,sz[i])-h);
        }
        while(q--)
        {
            scanf("%d%d%d",&ql,&qr,&k);
            int ans = query(T[ql-1],T[qr],1,num,k);
            printf("%d\n",h[ans]);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/WilliamSun0122/article/details/77871278
今日推荐