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

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

作者: a0f39b0b2030 | 来源:发表于2018-10-26 13:12 被阅读11次

主席树

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

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

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

比如有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 (动态不一样,之后会讲)

时间复杂度:mlog2nlog2n

hdu2665

题意

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

题解

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

#include

using namespace std;

typedef long long ll;

const int maxn = 1e5+5;

int T[maxn],L[maxn20],R[maxn20],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;

}

相关文章

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

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

  • 静态主席树

    主席树的作用是寻找一个序列的某个区间的第k大(小)如:给出一个序列a1,a2...an,有若干个询问,每个询问形如...

  • 静态主席树

  • Java常用设计模式

    工厂模式 这里主要讲解静态工厂,通过静态工厂根据需要创建不同的商品; 单例模式 1.单例提供的对象在全局只有一个;...

  • 常用的设计模式

    单例模式:Singleton 单例:静态变量,私有构造器,静态方法 分类: 懒汉单例 :在第一次调用的时候实例化自...

  • dispatch_once执行两次

    动态库A1调用静态库B中的单例方法C 初始化一次 静态库A2调用静态库B中的单例方法C 重新初始化一次 disp...

  • 可持久化线段树

    题目链接:可持久化数组 可持久化数组: 题目链接:Kth number 静态主席树: 题目链接:Dynamic R...

  • 关于单例模式

    静态实现单例模式能较少的使用内存,也具备一定的安全性 饿汉模式实现单例模式的原理是要一次单例对象就创建一个单例对象...

  • 我讲《一副名扬中外的画》

    从图片入手后,导入课题,穿插课题是量词➕形容词➕名词,让学生就伟大主席习近平为例,说说你会怎么命名,学生说:一位大...

  • HTTP Server优化

    Nginx 为例: 第一,缓存静态文件location ~*.(ico | jpg | jpeg | png | ...

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/hbvktqtx.html