可持久化线段树——主席树
主席树学习笔记
往期回顾:
线段树
什么是主席树?
主席树,即可持久化线段树,又称函数式线段树,是重要的可持久化数据结构之一。
主席树,学名可持久化线段树
指在修改线段树的过程中,把线段树的每一种历史版本全都记录下来
在记录每一个版本时,出现变化的点会分裂开,而重复的部分可以直接拿来用
于是形成了一个这样的结构
不同灰度的线连接起的都是这一颗线段树的不同版本
颜色相同的点表示由一个点分裂而来
不同的根节点就代表不同的版本
总之,主席树就是这样一种肥肠简 (du) 单 (liu) 的数据结构
(淦,学他整整花了我
主席树能解决的问题
主席树的用途其实还挺广的
但由于本蒟蒻对其知之甚少,不够了解主席树
没有总结出他的常见套路
不过总结出了一点东西的:
-
需要区间内容有序的,一般用主席树
eg:区间第 小数/区间内至少有 个数大于 -
想到了线段树的做法,但是要开好多棵线段树,用主席树优化
-
主席树还经常和前缀和思想、离散化 结合
注意:
主席树节点编号不能通过父节点计算出来
需要动态开点,并在结构体中存储左右儿子的节点编号
代码
-
两道模板题,都很经典
#include<iostream>
using namespace std;
const int N=1e6+10;
void read(int& x)
{
x=0;
int f=1;
char c;
c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=x*10+c-'0';
c=getchar();
}
x*=f;
}
void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
struct Tree{
int l,r;
int v;
}tr[50*N];
int n,m;
int w[N];
int root[N],idx;
int newroot(int u)
{
tr[++idx]=tr[u];
return idx;
}
int build(int u,int l,int r)
{
u=newroot(u);
if(l==r)
{
tr[u].v=w[l];
return u;
}
int mid=l+r>>1;
tr[u].l=build(tr[u].l,l,mid);
tr[u].r=build(tr[u].r,mid+1,r);
return u;
}
int update(int u,int l,int r,int k,int x)
{
u=newroot(u);
if(l==r)
{
tr[u].v=x;
return u;
}
int mid=l+r>>1;
if(k<=mid)
tr[u].l=update(tr[u].l,l,mid,k,x);
else
tr[u].r=update(tr[u].r,mid+1,r,k,x);
return u;
}
int query(int u,int l,int r,int k)
{
u=newroot(u);
if(l==r)
return tr[u].v;
int mid=l+r>>1;
if(k<=mid)
return query(tr[u].l,l,mid,k);
else
return query(tr[u].r,mid+1,r,k);
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
read(w[i]);
root[0]=build(0,1,n);
for(int i=1;i<=m;i++)
{
int vn,op,ps,vl;
read(vn),read(op),read(ps);
if(op==1)
{
read(vl);
root[i]=update(root[vn],1,n,ps,vl);
}else{
write(query(root[vn],1,n,ps));
puts("");
root[i]=root[vn];
}
}
return 0;
}
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=200020;
struct Tree{
int l,r;
int cnt;
}tr[N*22];
int n,m;
int root[N],idx;
int a[N];
vector<int> nums;
int find(int x)
{
return lower_bound(nums.begin(),nums.end(),x)-nums.begin();
}
int build(int l,int r)
{
int p=++idx;
if(l==r)
return p;
int mid=l+r>>1;
tr[p].l=build(l,mid);
tr[p].r=build(mid+1,r);
return p;
}
int update(int p,int l,int r,int k)
{
int q=++idx;
tr[q]=tr[p];
if(l==r)
{
tr[q].cnt++;
return q;
}
int mid=l+r>>1;
if(k<=mid)
tr[q].l=update(tr[p].l,l,mid,k);
else
tr[q].r=update(tr[p].r,mid+1,r,k);
tr[q].cnt=tr[tr[q].l].cnt+tr[tr[q].r].cnt;
return q;
}
int query(int p,int q,int l,int r,int k)
{
if(l==r)
return l;
int cnt=tr[tr[p].l].cnt-tr[tr[q].l].cnt;
int mid=l+r>>1;
if(k<=cnt)
return query(tr[p].l,tr[q].l,l,mid,k);
else
return query(tr[p].r,tr[q].r,mid+1,r,k-cnt);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
nums.push_back(a[i]);
}
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
root[0]=build(0,nums.size()-1);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],0,nums.size()-1,find(a[i]));
while(m--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",nums[query(root[r],root[l-1],0,nums.size()-1,k)]);
}
return 0;
}
结束啦~
主席树学习笔记就写到这里
还需要继续练题呀~!
加油💪哈
白白ヾ(•ω•`)o ~~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)