主席树总结(经典区间第k小问题)(主席树,线段树)
接着上一篇总结——可持久化线段树来整理吧。点击进入
这两种数据结构确实有异曲同工之妙。结构是很相似的,但维护的主要内容并不相同,主席树的离散化、前缀和等思想也要更难理解一些。
闲话
话说刚学习主席树的时候百度了一下,看到了“主席树”这一名字的由来——
线段树竟然是被一个黄嘉泰的大佬因不会划分树来代替的,,,,,因缩写是HJT取名为主席树= =!orz
我的内心瞬间感到了不安。。。。。。(看我的名字,就在右边)
能跟神犇有相同的缩写是何等荣幸!看来这个主席树我得好好学了。
那么接下来进入正题。
主席树
直接从最经典的应用——区间第小问题开始吧。ZSY巨佬对这一问题的思路挺清晰的,本蒟蒻在这里也参考一下。%ZSY%请点这里
1. 静态区间第k小问题
洛谷题目传送门
即给出一个序列,每次询问求给定区间内第小的值
思路分析
先不考虑每一个区间的情况,从最简单的查询整个区间的情况开始。
对数据离散化后,用一个线段树来维护,每个节点维护对应离散化后值区间的数的总个数。自上至下进行询问操作时,判断当前点左子树的与要查询的排名的大小关系。如果小于等于,就到左子树中找,不变。否则到右子树中找排名的值。这与平衡树(Splay,Treap)等查询给定排名数的方法是基本一样的。
那对于所有可能的区间,又该怎样维护呢?我们其实只要个线段树就好了,第个线段树维护的情况。这里我们利用了前缀和的性质。查询就等于查询减去的对应的没错吧。因为线段树是完全二叉树,具有结构稳定的性质,所以个线段树长得是一样的,对应区间相减是可行的。
然而暴力开个空间保准炸掉。这时候我们回头看看可持久化线段树是怎么做的。没错,从到也只变了一个值!于是同样只要新开个节点,保存到对应叶子节点的路径就OK了。
拿洛谷题目里的样例来几张图吧,好理解些。
首先是离散化后的序列。
我们一开始要建一棵空线段树,除了有个结构,所有的均为。然后一个一个的添加线段树。加入的线段树后会是这样:
再加入:
后面的手推一下吧。。。。。。
至此,棵树就建好了,并且只用了的空间。
查询的时候存两个点,一开始为和,两个的差与来比大小,两个点要同时往左/右跳。
更多细节就看代码吧。
#include<cstdio>
#include<algorithm>
using namespace std;
#define R register int
const int N=200009,M=5000009;
int P,a[N],b[N],rt[N],lc[M],rc[M],s[M];
#define G c=getchar()
inline void in(R&z)
{
register bool f=0;
register char G;
while(c<'-')G;
if(c=='-')f=1,G;
z=c&15;G;
while(c>'-')z=(z<<3)+(z<<1)+(c&15),G;
if(f)z=-z;
}//快读
void build(R&t,R l,R r)
{
t=++P;
if(l!=r)
{
R m=(l+r)>>1;
build(lc[t],l,m);
build(rc[t],m+1,r);
}
}//线段树操作,建一个空线段树
inline void insert(R*t,R u,R l,R r,R v)
{
while(l!=r)
{
s[*t=++P]=s[u]+1;//注意这里要+1
R m=(l+r)>>1;
if(v<=m)r=m,rc[*t]=rc[u],t=&lc[*t],u=lc[u];
else l=m+1,lc[*t]=lc[u],t=&rc[*t],u=rc[u];
}
s[*t=++P]=s[u]+1;
}//插入操作在可持久化线段树总结里面有更详细的介绍
inline int ask(R t,R u,R l,R r,R k)
{
while(l!=r)
{
R m=(l+r)>>1,v=s[lc[u]]-s[lc[t]];//作差
if(k<=v)r=m,t=lc[t],u=lc[u];//两个点一起跳
else l=m+1,t=rc[t],u=rc[u],k-=v;
}
return b[l];
}
int main()
{
R n,m,i,l,r,sz;
in(n);in(m);
for(i=1;i<=n;++i)
in(a[i]),b[i]=a[i];
sort(b+1,b+n+1);
sz=unique(b+1,b+n+1)-b-1;//离散化,排序去重
build(rt[0],1,sz);
for(i=1;i<=n;++i)
insert(&rt[i],rt[i-1],1,sz,lower_bound(b+1,b+sz+1,a[i])-b);//直接用STL的二分找对应值了
while(m--)
{
in(l);in(r);in(i);
printf("%d\n",ask(rt[l-1],rt[r],1,sz,i));
}
return 0;
}
2. 动态区间第k小问题
洛谷题目传送门
就是比上面那题多了个修改。。。。。。
改为树状数组维护前缀和,使修改复杂度降低。
详细题解在此
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析