[线段树系列 #5] 可持久化线段树

[线段树系列 #5] 可持久化线段树

简单介绍

可持久化线段树,为什么是可持久化的呢,因为他能调取线段树上的历史数据。

思路概述

可持久化线段树的简单思路就是,在动态开点线段树的基础上,每次修改时建一棵新树,并将没有修改的节点直接指向原有的节点,使得可以在较短的时间内完成操作。这里插一张网上的图,辅助理解。

可持久化数据结构,一般用来求区间的一个信息(如经典例题求区间第k小数)或是调取历史信息。

而在求区间信息时,大多为在询问时一边向更深节点走一边限制边界。

而可持久化结构所给予的支持即为 1.调取[1~r]段的信息 2.调取[1~l-1]段的边界

具体实现

通常操作为:离散化后先按照值域建一棵空树,然后将每个节点依次插入,最后开始处理操作

插入节点时,按上述思路来说即为新建一条链,所以具体实现上为先继承上个版本当前点的左右儿子,然后按照值的大小选择新建节点为左儿子或右儿子。

在下面的例题P3834 【模板】可持久化线段树 2,即求静态 \([l,r]\) 的第 \(k\) 小值中,我们通过可持久化线段树的区间可加减性,由 \([1,r]-[1,l-1]\) 求得 \([l,r]\) 的答案

// 例题:求[l,r]第k小值
void build(int &p, int l, int r){
	p = ++cnt;
	if(l == r) return;
	int mid = (l+r) >> 1;
	build(t[p].l, l, mid);
	build(t[p].r, mid+1, r);
}
void update(int &p, int lst, int l, int r, int x){
	p = ++cnt;
	t[p] = t[lst]; t[p].val++;
	if(l == r) return;
	int mid = (l+r) >> 1;
	if(x <= mid) update(t[p].l, t[lst].l, l, mid, x);
	else update(t[p].r, t[lst].r, mid+1, r, x);
}
int query(int L, int R, int l, int r, int k){
	if(l == r) return l;
	int mid = (l+r) >> 1, x = t[t[R].l].val-t[t[L].l].val;
	if(x >= k) return query(t[L].l, t[R].l, l, mid, k);
	else return query(t[L].r, t[R].r, mid+1, r, k-x);
}

而在下一道例题,P2633 Count on a tree,即求树上两点间点权的第 \(k\) 小值中,我们也同样可以利用上述性质,结合树上差分,答案即为\([1,u]+[1,v]-[1-lca]-[1,fa[lca]]\)。具体实现即为修改在 \(query\) 操作中的 \(x\) 变量进行限制。

void update(int &p, int q, int l, int r, int v){
	p = ++cnt;
	t[p] = t[q]; t[p].val++;
	if(l == r) return;
	int mid = (l+r) >> 1;
	if(v <= mid) update(t[p].l, t[q].l, l, mid, v);
	else update(t[p].r, t[q].r, mid+1, r, v);
}
int query(int a, int b, int c, int d, int l, int r, int k){
	if(l == r) return l;
	int mid = (l+r) >> 1, x = t[t[a].l].val+t[t[b].l].val-t[t[c].l].val-t[t[d].l].val;
	if(k <= x) return query(t[a].l, t[b].l, t[c].l, t[d].l, l, mid, k);
	else return query(t[a].r, t[b].r, t[c].r, t[d].r, mid+1, r, k-x);
}
void bfs(){
	queue <int> q;
	q.push(1);
	d[1] = 1;
	while(q.size()){
		int t = q.front(); q.pop();
		for(int i = hd[t]; i; i = nxt[i]){
			int y = ver[i];
			if(d[y]) continue;
			update(rt[y], rt[t], 1, len, v[y]);
			d[y] = d[t]+1, f[y][0] = t;
			for(int j = 1; j <= 20; j++)
				f[y][j] = f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
void solve(){
    lsh();
	build(rt[0], 1, len);
	update(rt[1], rt[0], 1, len, v[1]);
	bfs();
}

而维护可持久化线段树过程中,则从原来的 \(update(rt[i],rt[i-1],...)\) 变为 \(update(rt[u],rt[fa[u]],...)\)。这也是可持久化线段树多种运用的一个举例,即通过一些前后信息来建树。

posted @ 2025-07-17 10:15  Hirasawayuiii  阅读(16)  评论(0)    收藏  举报