可持久化线段树

参见《算法竞赛》。

以静态区间第 \(k\) 小值为例来讲解主席树。

首先,考虑 \([1,i]\) 中第 \(k\) 小值的解法。

显然,可以权值线段树二分,利用 BST 的思想求解。

那么考虑对于 \(i\in [1,n]\),求解 \([1,i]\) 中第 \(k\) 小值。

为了简化问题,令 \(n=2^k\),其中 \(k\) 为非负整数。

对于 \(i=1\) 的问题,我们习惯用一棵大小为 \(1\) 的线段树来解决(从线段树的角度看问题)。但是这时用一棵大小为 \(2^{k+1}-1\) 的线段树来解决,即一棵满二叉树,有 \(2^k\) 个叶子节点,参见《算法竞赛》第 204 页。

为什么要这么做?

因为这样,我们对于 \(i=n=2^k\) 的问题也可以在这同一棵线段树上解决。

注意,这仍然是一棵权值线段树。

参见《算法竞赛》第 204 页,来模拟 \(i\in [1,n]\) 的权值线段树情况。

发现当将 \(a_i\) 加入到权值线段树中时,修改的路径是树上的一条从根走到叶子的链,每次修改只会改变 \(k+1\) 个节点,即树的深度。

设当前加入的是 \(a_i\),我们可以很快捷的利用线段树二分求出 \([1,i]\) 的第 \(k\) 小值;现在再来思考 \([L,R]\) 的第 \(k\) 小值求法。

为了解决这个问题,我们先假设对于 \(i\in [1,n]\),每个 \([1,i]\) 都开一个权值线段树。(刚才只开了一棵线段树)

《算法竞赛》第 205 页有极为详细的描述。

考虑对于权值线段树上的一个节点,下标中的 \([l,r]\) 代表着它所维护的区间;其权值代表该区间中的元素个数。

\(f(x)\) 代表当加入 \(a_x\) 时的线段树,即 \(x\) 这一历史版本。

显然,\(f(R),[l,r]\) 代表 \(R\) 这一历史版本中 \([l,r]\) 这一节点的权值。更通俗的讲,就是 \([1,R]\) 这段区间中 \([l,r]\) 的元素个数,注意,这里 \(l,r\) 均指权值。

同理,我们可以用 \(f(L-1),[l,r]\) 得到 \([1,L-1]\)\([l,r]\) 的元素个数。

那么,利用前缀和的思想,容易得到 \([L,R]\)\([l,r]\) 的元素个数即为 \(f(R)-f(L-1),[l,r]\)

所以我们直接对线段树上对应节点做相减操作,就得到了 \(f([L,R])\)

我们把 \(f(R)-f(L-1)=f([L,R])\) 这一运算称为线段树减法

目前来看,得到 \(f([L,R])\) 需要线段树大小的时间复杂度。但是,将其与区间第 \(k\) 小值联系在一起,我们发现只需要对查询路径及路径上节点的儿子做减法即可。于是复杂度变成了 \(O(\log n)\)

单次查询 \(O(\log n)\) ,这已经是一个相当优秀的复杂度,但是由于我们需要建 \(n\) 棵线段树,空间复杂度会高达 \(n^2\),并且空间常数不小。同时,进行 \(n\) 次建树的时间复杂度会到达 \(O(n^2)\)

考虑优化。;

还记得一开始所说的,在一棵树上操作时,每次只需要改变 \(k+1\) 个节点吗?

显然,\(f(i+1)\)\(f(i)\) 的不同之处就在这 \(k+1\) 个节点,于是我们只需要将这 \(k+1\) 个节点与前一棵树做区分即可。

如何做区分呢?

参见《算法竞赛》第 205 页。

我们新建 \(\log\) 个点,将其与 \(f(i-1)\) 连边,记录一下当前根节点即可。

但正如第 206 页所说,我们并不需要建一棵初始空树,用一棵在结构和逻辑上均不完整的线段树也能得出答案。

关于可持久化数组,也可以用主席树维护。把叶子节点设为权值,其余节点设为空,然后按照主席树的方式维护即可。

但是这样维护稍显多余,无论是时间还是空间都给人可以优化的感觉。一种比较好的方法,是按照题解区 Elagia的做法,离线解决问题。

posted @ 2024-02-11 00:15  BYR_KKK  阅读(8)  评论(0编辑  收藏  举报