[主席树/可持久化线段树] 主席树讲稿

前置知识

线段树

可持久化

这是相对于“瞬间”的概念:
使用线段树就,进行一个操作线段树就完全变化
但如果我们需要进行这个操作之前的线段树状态,难道还要退回去?直接T飞

于是考虑以空间换时间:把每个线段树都记录下来,需要调用前面的状态时,直接访问
这就是可持久化线段树:能支持访问以往某个版本的数据的数据结构
查之前状态的时间复杂度 Θ ( N ) → Θ ( 1 ) \Theta(N)\to\Theta(1) Θ(N)Θ(1)
总时间复杂度: Θ ( ( n + m ) log ⁡ n ) \Theta((n+m)\log n) Θ((n+m)logn)

考虑优化空间:
由于每次修改之改了一条链/一个子树,其它的部分都没有变,于是直接把没有变的儿子连到新父亲身上
这也决定了我们必须采用动态加点的方式建树
空间复杂度 Θ ( ( n + m ) log ⁡ n ) \Theta((n+m)\log n) Θ((n+m)logn)
数组多开20倍够了,卡空间18倍也行


树上的东西都是空的,只起到传递的作用
只有叶子节点有意义(单点改单点差)

#include<bits/stdc++.h>
#define in Read()
#define re register
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=2e5+10;
int n,m,size/*节点个数*/;
int a[NNN],b[NNN],Root[NNN]/*记录每个区间的根节点*/;
struct node{
	int lch,rch,sum;
}tree[NNN<<5];//主席树开32倍空间 

inline int build(int l,int r){
	int root=++size;
	if(l==r)return root;
	int mid=(l+r)>>1;
	tree[root].lch=build(l,mid);
	tree[root].rch=build(mid+1,r);
	return root;
}

inline int Update(int pre,int l,int r,int x){
	int root=++size;
	//新节点继承原节点的所有信息; 
	tree[root].lch=tree[pre].lch;
	tree[root].rch=tree[pre].rch;
	tree[root].sum=tree[pre].sum+1;
	
	if(l==r)return root;
	int mid=(l+r)>>1;
	if(x<=mid)tree[root].lch=Update(tree[pre].lch,l,mid,x);
	else tree[root].rch=Update(tree[pre].rch,mid+1,r,x);
	return root;
}

inline int Query(int u,int v/*目前相减两区间的根节点*/,int l,int r,int k){
	//l==r,搜到
	if(l==r)return l;
	
	//两个前缀和相减得到区间的左子树的权值大于k则第k大数在左边,反之在右边 
	int x=tree[tree[v].lch].sum-tree[tree[u].lch].sum,mid=(l+r)>>1;
	if(x>=k)return Query(tree[u].lch,tree[v].lch,l,mid,k);
	else return Query(tree[u].rch,tree[v].rch,mid+1,r,k-x);//细节:左边的权值都不算了 
}

int main(){
	n=in,m=in;
	
	//离散化 
	for(re int i=1;i<=n;++i)b[i]=a[i]=in;
	std::sort(b+1,b+n+1);
	int cnt=std::unique(b+1,b+n+1)-b-1;//这个东西是实际需要的叶子结点的个数 
	for(re int i=1;i<=n;++i)a[i]=std::lower_bound(b+1,b+cnt+1,a[i])-b;
	
	//建树与更新 
	Root[0]=build(1,cnt);
	for(re int i=1;i<=n;++i)Root[i]=Update(Root[i-1],1,cnt,a[i]);
	
	//查询
	for(re int i=1;i<=m;++i){
		int l=in,r=in,k=in;
		printf("%d\n",b[Query(Root[l-1],Root[r],1,cnt,k)]);//Query返回的是离散化之后的编号,要用b数组映射回去 
	}
	return 0;
}

当时第一遍没用传值写,就写挂了
重构用传值写就写好了

主席树

江湖人传发明这种数据结构的神犇黄嘉泰,因为他在考试的时候不会写归并树就写了主席树这种东西替代归并树,并成功让广大OIer使用了这种数据结构,把归并树扔进了垃圾箱。因为他的名字缩写HJT也是当时chairman的名字缩写,故称为主席树。

其实就是可持久化线段树

操作建树

序列
纯朴的访问以前的状态(但是据说很少考)

序列权值建树

第k大
→ \to 权值线段树(可以理解为:操作就是加数字进去,然后前缀和)
离散化

主席树常见题型


主席树再进阶就是与其它数据结构结合
比如可持久trie,树上,树套树,虚树等等

练习

谈笑风生
题解

Middle
题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值