主席树(可持久化线段树)学习笔记

本文介绍了主席树和可持久化线段树的区别,强调了在处理动态修改和查询需求时,主席树通过保存不同时期的线段树根节点来减少重复计算,提供了一种高效的数据结构解决方案。文章还给出了相应的模板和示例应用。

主席树(可持久化线段树)

我真不知道这俩有啥区别…


前置思想

动态开点,前缀和,标记永久化。


思想

在修改线段树时,我们发现不同时期的线段树有大量重合,可以只改变线段树的一部分就做到一次更新整个的操作,这一部分通常只有 log ⁡ n \log{n} logn 级别。

每次从根开始修改,但是不直接覆盖,而是每次都增加一个新的根,并存入一个 r o o t root root 数组,这样就能做到查询不同时期的树。

在修改时,我们直接将原本的点复制到新点上,修改要修改的部分,这样就可以做到既不影响之前,也能做到更新。

主席树模版:

struct Persistable_Segment_Tree{
	#define ls(p) (tr[p].ls)
	#define rs(p) (tr[p].rs)
	#define mid(p) (tr[p].l+tr[p].r>>1)
	int tot,n;//动态开点
	int root[N];//存储不同时期的线段树的根,然后就可以查询了
	struct node{
		int l,r,len;//这些必要时也可以去掉
	}tr[N*25];//空间一般开2^5倍,除非题目卡空间
	void init(int _n){
		n=_n;tot=root[0]=1;
		build(root[0],1,n);
	}
	void build(int p,int l,int r){
		tr[p]={l,r,r-l+1};
		if(l==r)return;
		ls(p)=++tot,rs(p)=++tot;
		build(ls(p),l,mid(p));
		build(rs(p),mid(p)+1,r);
	}
	void update(int x,int d,int p,int q){
		tr[q]=tr[p];//新建节点,直接复制,这样就相当于新建一棵只改变这一条路径的线段树
		if(tr[q].len==1)return;
		if(x<=mid(p))ls(q)=++tot,update(x,d,ls(p),ls(q));
		else rs(q)=++tot,update(x,d,rs(p),rs(q));
	}//这里展示的是单点修改
	int query(int p,int x){
		if(tr[p].len==1)return ...;
		if(x<=mid(p))return query(ls(p),x);
		return query(rs(p),x);
	}//单点查询
}seg;

更多操作还是看例题吧。


模版题

  1. 单点修改+单点查询

P3919 【模板】可持久化线段树 1(可持久化数组) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn).

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<ctime>
#include<sstream>
#define INF 0x3f3f3f3f
#define ll long long
#define rg register
#define min(a,b) ((a)>(b)?(b):(a))
#define max(a,b) ((a)<(b)?(b):(a))
#define tomax(a,b) ((a)=max((a),(b)))
#define tomin(a,b) ((a)=min((a),(b)))
#define FOR(i,a,b) for(rg int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(rg int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=1e6+10,M=1e6+10;
int n,m,a[N];
struct Persistable_Segment_Tree{
	#define ls(p) (tr[p].ls)
	#define rs(p) (tr[p].rs)
	int tot,n;
	int root[N];
	struct node{
		int l,r,len;
		int ls,rs;
		int sum;
	}tr[N*25];
	void init(int _n){
		n=_n;tot=root[0]=1;
		build(root[0],1,n);
	}
	#define mid(p) (tr[p].l+tr[p].r>>1)
	void build(int p,int l,int r){
		tr[p]={l,r,r-l+1,-1,-1,0};
		if(l==r)return tr[p].sum=a[l],void();
		ls(p)=++tot,rs(p)=++tot;
		build(ls(p),l,mid(p));
		build(rs(p),mid(p)+1,r);
	}
	void update(int x,int d,int p,int q){
		tr[q]=tr[p];
		if(tr[q].len==1)return tr[q].sum=d,void();
		if(x<=mid(p))ls(q)=++tot,update(x,d,ls(p),ls(q));
		else rs(q)=++tot,update(x,d,rs(p),rs(q));
	}
	int query(int p,int x){
		if(tr[p].len==1)return tr[p].sum;
		if(x<=mid(p))return query(ls(p),x);
		return query(rs(p),x);
	}
}seg;
signed main(){
	cin>>n>>m;
	FOR(i,1,n)cin>>a[i];
	seg.init(n);
	FOR(i,1,m){
		int pre,opt,x;cin>>pre>>opt>>x;
		if(opt==1){
			int d;cin>>d;
			seg.root[i]=++seg.tot;
			seg.update(x,d,seg.root[pre],seg.root[i]);
		}else {
			seg.root[i]=seg.root[pre];
			cout<<seg.query(seg.root[pre],x)<<endl;
		}
	}
	return 0;
}
  1. 单点修改+区间二分(查询)

Kth number,十分经典的题目。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=1e5+10;
int T;
int n,m,a[N];
int b[N],cnt;
struct Persistable_Segment_Tree{
	#define ls(p) (tr[p].ls)
	#define rs(p) (tr[p].rs)
	int tot,n;
	int root[N];
	struct node{
		int ls,rs;
		int sum;
	}tr[N<<5];
	void init(int _n){
		n=_n;tot=0;
		root[0]=build(1,n);
	}
	#define mid (l+r>>1)
	int build(int l,int r){
		int p=++tot;
		tr[p]={-1,-1,0};
		if(l==r)return p;
		ls(p)=build(l,mid),rs(p)=build(mid+1,r);
		return p;
	}
	int update(int p,int x,int l,int r){
		int q=++tot;
		tr[q]=tr[p];++tr[q].sum;
		if(l==r)return q;
		if(x<=mid)ls(q)=update(ls(p),x,l,mid);
		else rs(q)=update(rs(p),x,mid+1,r);
		return q;
	}
	int query(int p,int q,int k,int l,int r){
		if(l==r)return l;
		int x=tr[ls(q)].sum-tr[ls(p)].sum;
		if(x>=k)return query(ls(p),ls(q),k,l,mid);
		return query(rs(p),rs(q),k-x,mid+1,r);
	}
	#undef mid
}seg;
void Cu_main(){
	cin>>n>>m;
	FOR(i,1,n)cin>>a[i],b[i]=a[i];
	sort(b+1,b+n+1);
	cnt=unique(b+1,b+n+1)-b-1;
	seg.init(cnt);
	FOR(i,1,n){
		a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
		seg.root[i]=++seg.tot;
		seg.root[i]=seg.update(seg.root[i-1],a[i],1,cnt);
	}
	while(m--){
		int l,r,k;cin>>l>>r>>k;
		cout<<b[seg.query(seg.root[l-1],seg.root[r],k,1,cnt)]<<endl;
	}
}
signed main(){
	for(cin>>T;T;--T)Cu_main();
	return 0;
}/*权值线段树->主席树*/
  1. 区间修改+区间(单点)查询

To the moon - HDU 4348 - Virtual Judge (vjudge.net).

#include<iostream>
#define ll long long
#define int long long
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=1e5+10;
int T,cas;
int n,m;
ll a[N];
struct Persistable_Segment_Tree{
	#define ls(p) (tr[p].ls)
	#define rs(p) (tr[p].rs)
	int tot,n,root[N];
	struct node{
		int ls,rs;
		ll sum,mark;
	}tr[N<<4];
	void init(int _n){
		n=_n;tot=root[0]=0;
		build(root[0],1,n);
	}
	#define mid (L+R>>1)
	void push_up(int p,int len){
		tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum+1ll*tr[ls(p)].mark*(len-len/2)+1ll*tr[rs(p)].mark*(len/2);
	}
	void build(int &p,int L,int R){
		p=++tot;tr[p]={0,0,0,0};
		if(L==R)return tr[p].sum=a[L],void();
		build(ls(p),L,mid),build(rs(p),mid+1,R);
		push_up(p,R-L+1);
	}
	void update(int &p,int q,int l,int r,int d,int L,int R){
		p=++tot;tr[p]=tr[q];
		if(l<=L&&R<=r)return tr[p].mark+=d,void();
		if(l<=mid)update(ls(p),ls(q),l,r,d,L,mid);
		if(mid<r)update(rs(p),rs(q),l,r,d,mid+1,R);
		push_up(p,R-L+1);
	}
	ll query(int p,int l,int r,ll mark,int L,int R){
		if(l<=L&&R<=r)return tr[p].sum+1ll*(tr[p].mark+mark)*(R-L+1);
		ll ans=0;
		if(l<=mid)ans+=query(ls(p),l,r,mark+tr[p].mark,L,mid);
		if(mid<r)ans+=query(rs(p),l,r,mark+tr[p].mark,mid+1,R);
		return ans;
	}
	#undef mid
}seg;
void Cu_main(){
	if(cas)cout<<endl;cas=1;
	FOR(i,1,n)cin>>a[i];
	seg.init(n);T=0;
	while(m--){
		char opt;cin>>opt;
		if(opt=='C'){
			int l,r,d;cin>>l>>r>>d;++T;
			seg.update(seg.root[T],seg.root[T-1],l,r,d,1,n);
		}else if(opt=='Q'){
			int l,r;cin>>l>>r;
			cout<<seg.query(seg.root[T],l,r,0,1,n)<<endl;
		}else if(opt=='H'){
			int l,r,t;cin>>l>>r>>t;
			cout<<seg.query(seg.root[t],l,r,0,1,n)<<endl;
		}else {
			cin>>T;
			seg.tot=seg.root[T+1];
		}
	}
}
signed main(){
	while(cin>>n>>m)Cu_main();
	return 0;
}

相关资料

  1. 可持久化线段树 - OI Wiki (oi-wiki.org)
  2. 可持久化线段树(主席树)攻略 - 知乎 (zhihu.com).
  3. 算法学习笔记(50): 可持久化线段树 - 知乎 (zhihu.com).
  4. 数据结构学习笔记(7) 主席树 - 知乎 (zhihu.com).
  5. 「主席树」和「可持久化线段树」有什么区别? - 知乎 (zhihu.com).
  6. 良心的可持久化线段树教程 - 胡小兔 - 博客园 (cnblogs.com).

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值