(笔记)可持久化数据结构
简记
可持久化
一种允许你查询并使用历史版本的数据结构,其家族成员包括但不限于可持久化平衡树,线段树,并查集(以可持久化线段树为基础)等。
线段树
适用于一些单点修改的题上。
其基本思路是将线段树动态开点后,先建一颗完整的线段树(如果没有初值也可以不建),然后每次更新链上的节点时,新建节点并将不修改部分指向先前的线段树上的节点。
具体地,如果要修改 \(ls\) 且 \(ls\) 为 \(p\) 的左儿子,\(rs\) 为 \(p\) 的右儿子,那么新建节点 \(newls\),\(newp\),令 \(newp\rightarrow rs\),\(newp\rightarrow newls\) 即可。
上述思想在例题代码中体现:
#include<bits/stdc++.h>
using namespace std;
const int INF=1<<30,N=1e6+5;
struct Tre{
int v,ls,rs;
}t[N*24];
int tot,rt[N],n,m,a[N];
#define mid ((l+r)>>1)
int build(int l,int r){
int u=++tot;
if(l==r){t[u].v=a[l];return u;}
t[u].ls=build(l,mid);
t[u].rs=build(mid+1,r);
return u;
}
int query(int u,int l,int r,int q){
if(l==r)return t[u].v;
if(q<=mid)return query(t[u].ls,l,mid,q);
return query(t[u].rs,mid+1,r,q);
}
int update(int u,int l,int r,int p,int val){
int nu=++tot;
t[nu]=t[u];
if(l==r){t[nu].v=val;return nu;}
if(p<=mid)t[nu].ls=update(t[u].ls,l,mid,p,val);
else t[nu].rs=update(t[u].rs,mid+1,r,p,val);
return nu;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
rt[0]=build(1,n);
for(int i=1;i<=m;i++){
int ver,opt,loc,val;
cin>>ver>>opt>>loc;
if(opt==1){
cin>>val;
rt[i]=update(rt[ver],1,n,loc,val);
}
else {
cout<<query(rt[ver],1,n,loc)<<'\n';
rt[i]=rt[ver];
}
}
return 0;
}
标记永久化
类似的把自己的儿子指针接在之前的版本上的思想,使其不能够直接在树上进行区间改操作(可能影响到之前的版本),所以就出现了标记永久化。
具体来说,线段树区间改update
时,仅仅将区间打上一个tag
并且不进行pushdown
,每次查询时加上该tag
即可。
包括不使用可持久化线段树时,该 trick 依旧适用,例如 P8543 「Wdoi-2」纯粹的复仇女神