线段树进阶
线段树进阶#
权值线段树#
权值线段树的思想就是让线段树存储的东西由下标变成了权值
也就是区间 内实际上存储的是权值在 范围内的数的个数
权值线段树有诸多用途,如我们可以查询 来找到所有权值小于 的数的个数
可持久化线段树与可持久化权值线段树 (主席树)#
可持久化线段树,俗称主席树,是一种非常强大的数据结构
假设每次操作得到的都是一个版本,通过可持久化,我们可以方便的查询每一个版本的内容
若是对于每次操作我们都建一棵线段树,这样很显然空间会爆炸
主席树就很好的解决了这一点
假设有 个点 次操作,主席树的核心思想就是:
由于每次修改的区间都对应线段树某一条从叶子节点到根的链,其余所有节点信息不变
因此我们新建一条链,让其所有节点的左右儿子以及父亲的信息与被修改的节点一样,并且让它们所维护的信息为修改后的信息
显然一条链最多 个点,空间复杂度就是 ,时间复杂度是 的
注意:需要动态开点,不要吝啬空间
假如我们修改 这条链,树就会变成这样:
上几道例题
P3919 【模板】可持久化线段树 1(可持久化数组)#
这个题要求在历史版本上修改和查询
直接上可持久化线段树
#include<bits/stdc++.h>
using namespace std;
#define def register auto
const int N=2e6+5;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m;
int a[N],rt[N];
namespace tree{
struct node{
int ls,rs;
int v;
}t[N*20];
int cnt;
inline void build(int &p,int l,int r){
p=++cnt;
if(l==r){
t[p].v=a[l];
return;
}
def mid=l+r>>1;
build(t[p].ls,l,mid);
build(t[p].rs,mid+1,r);
}
inline void insert(int &p,int pre,int l,int r,int x,int k){
p=++cnt;
t[p]=t[pre];
if(l==r){
t[p].v=k;
return;
}
def mid=l+r>>1;
if(x<=mid) insert(t[p].ls,t[pre].ls,l,mid,x,k);
else insert(t[p].rs,t[pre].rs,mid+1,r,x,k);
}
inline int query(int p,int l,int r,int x){
if(l==r) return t[p].v;
int mid=l+r>>1;
if(x<=mid) return query(t[p].ls,l,mid,x);
return query(t[p].rs,mid+1,r,x);
}
}
signed main(){
n=read(),m=read();
for(def i=1;i<=n;++i) a[i]=read();
tree::build(rt[0],1,n);
for(def i=1;i<=m;++i){
def pre=read(),op=read();
if(op==1){
def x=read(),k=read();
tree::insert(rt[i],rt[pre],1,n,x,k);
}
else{
def x=read();
printf("%d\n",tree::query(rt[pre],1,n,x));
rt[i]=rt[pre];
}
}
}
P3834 【模板】可持久化线段树 2#
典中典 静态区间第 小
考虑用可持久化权值线段树完成此题
先离散化,然后我们对每一个前缀数列建立一个版本
查询区间 的时候,我们直接用第 个版本的值减去第 个版本的值就可以得到
具体细节见代码:
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,m;
int a[N];
vector <int> p;
struct node{
int l,r;
int cnt;
}t[N*20];
int root[N],id;
inline int find(int x){
return lower_bound(p.begin(),p.end(),x)-p.begin();
}
inline int build(int l,int r){
int p=++id;
if(l==r) return p;//返回节点编号
int mid=l+r>>1;
t[p].l=build(l,mid);
t[p].r=build(mid+1,r);
return p;
}
inline int insert(int now,int l,int r,int x){
int p=++id;
t[p]=t[now];//复制版本
++t[p].cnt;//x出现的次数+1
if(l==r)
return p;
int mid=l+r>>1;
if(x<=mid) t[p].l=insert(t[now].l,l,mid,x);
else t[p].r=insert(t[now].r,mid+1,r,x);
return p;
}
inline int query(int q,int p,int l,int r,int k){//l,r表示值域
if(l==r) return l;
int cnt=t[t[q].l].cnt-t[t[p].l].cnt;//版本[p,q]中1~l中的数出现的次数
int mid=l+r>>1;
if(k<=cnt) return query(t[q].l,t[p].l,l,mid,k);//排名第k的在[l,mid]范围内
else return query(t[q].r,t[p].r,mid+1,r,k-cnt);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
p.push_back(a[i]);
}
sort(p.begin(),p.end());
p.erase(unique(p.begin(),p.end()),p.end());
root[0]=build(0,p.size()-1);//建立0版本的空树
for(int i=1;i<=n;++i){
int x=find(a[i]);
root[i]=insert(root[i-1],0,p.size()-1,x);//对每一个前缀数列建立一个版本
}
while(m--){
int l,r,k;
cin>>l>>r>>k;
cout<<p[query(root[r],root[l-1],0,p.size()-1,k)]<<endl;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)