可持久化线段树学习笔记

嗯终于来填坑了。那就只剩平衡树了。


就是给线段树多开几个根,每个根对应一颗线段树,不过只有不同的信息才会再记录一次。

非常好理解的。


洛谷 P3919 | 可持久化数组

可持久化数组的模板题,是一个比较常见的应用。

图片详解,就不具体解释了,理解了线段树之后这玩意是很好理解的,所以重点在于如何小常数地实现。

模板代码:

class __persistent_segment_tree
{
#define mid (l+r>>1)
	
private:
	struct node{signed lc,rc; int val;} v[N<<5]; // 用 signed 存节点编号能省一部分空间。N<<5 是因为都这么写,类似于线段树的 N<<2
	int tot=0,*a,n,lt,rt,t,k;
	
	il void build(int l,int r,signed& u) // 需要修改的节点 u 传引用
	{
		u=++tot; // 动态开点
		if(l==r) return v[u]={0,0,a[l]},void();
		build(l,mid,v[u].lc),build(mid+1,r,v[u].rc);
	}
	
	il void update(int l,int r,signed& u,signed p) // 意思是节点 u 在操作前本来是 p
	{
		u=++tot,v[u]=v[p]; // u 需要先复制一份 p 的值
		if(l==r) return v[u]={0,0,k},void();
		mid>=t ? update(l,mid,v[u].lc,v[u].lc) : update(mid+1,r,v[u].rc,v[u].rc); // 然后根据修改点的位置更新 u 的左或右儿子
	}
	
	il int query(int l,int r,signed u)
	{
		if(l==r) return v[u].val;
		return mid>=t ? query(l,mid,v[u].lc) : query(mid+1,r,v[u].rc);
	}
	
public:
	int cnt=0; signed root[N]; // 把 root 数组放 public 是因为有可能在外部新建根但并不是在 update 操作中
	il void bui(int *_a,int _n){a=_a,n=_n; build(1,n,root[0]);}
	il void upd(int rt,int _t,int _k){t=_t,k=_k; update(1,n,root[++cnt],root[rt]);}
	il int q(int rt,int _t){t=_t; return query(1,n,root[rt]);}
	
#undef mid
} tr;

submission

天依宝宝可爱!


洛谷 P3834 | 主席树

主席树(可持久化权值线段树)模板题,在线静态区间 \(k\) 小也是一个常见的应用。

先考虑一个弱化版,就是求 \([1,r]\) 的区间 \(k\) 小,这个只需要在主席树第 \(r\) 个根所代表的树上二分就可以了。

那么尝试推广到 \([l,r]\) 上,发现也是容易的,因为是权值线段树,所以下标在区间 \([l,r]\) 内,值在区间 \([L,R]\) 的数的个数 可以通过 \([1,r]\) 值在区间 \([L,R]\) 的数的个数 减去 \([1,l-1]\) 的 来计算。于是还是二分,不过要在第 \(r\) 个根代表的树和第 \(l-1\) 个根代表的树上同时二分。

或者说,这个东西叫线段树差分。

模板代码:

class __persistent_segment_tree
{
#define mid (l+r>>1)
	
private:
	struct node{signed lc,rc; int cnt;} v[N<<5];
	int tot=0,*a,n,lt,rt,t,k;
	
	il void pu(signed u){v[u].cnt=v[v[u].lc].cnt+v[v[u].rc].cnt;}
	
	il void update(int l,int r,signed& u,signed p)
	{
		u=++tot,v[u]=v[p];
		if(l==r) return ++v[u].cnt,void();
		mid>=t ? update(l,mid,v[u].lc,v[u].lc) : update(mid+1,r,v[u].rc,v[u].rc);
		pu(u);
	}
	
	il int query(int l,int r,signed ul,signed ur) // ul 为第 l-1 个根代表的树的值域区间 [L,R] 对应节点,ur 为第 r 个根代表的树的值域区间 [L,R] 对应节点(这里的 L,R 对应代码中的 int l,int r)
	{
		if(l==r) return l;
		int tmp=v[v[ur].lc].cnt-v[v[ul].lc].cnt; // 询问区间内值在 [L,mid] 内数的个数
		return tmp>=k ? query(l,mid,v[ul].lc,v[ur].lc) : (k-=tmp,query(mid+1,r,v[ul].rc,v[ur].rc));
	}
	
public:
	int cnt=0; signed root[N];
	il void bui(int _n){n=_n;}
	il void upd(int _t){t=_t; ++cnt,update(0,n,root[cnt],root[cnt-1]);} // 注意 P3834 中 a_i 最小值为 0
	il int q(int l,int r,int _k){k=_k; return query(0,n,root[l-1],root[r]);}
	
#undef mid
} tr;

il void solve()
{
	read(n,m),_::r(a,n);
	
	tr.bui(mxele(a+1,a+n+1));
	rep(i,1,n) tr.upd(a[i]); // 没有建树操作,而是依次加入每个点
	
	int l,r,k;
	while(m--) read(l,r,k),write(tr.q(l,r,k),'\n');
}

submission

天依宝宝可爱!

posted @ 2025-10-20 11:43  little__bug  阅读(6)  评论(0)    收藏  举报