可持久化线段树学习笔记
嗯终于来填坑了。那就只剩平衡树了。
就是给线段树多开几个根,每个根对应一颗线段树,不过只有不同的信息才会再记录一次。
非常好理解的。
洛谷 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;
天依宝宝可爱!
洛谷 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');
}
天依宝宝可爱!

浙公网安备 33010602011771号