关于 WBLT

WBLT(Weight Balanced Leafy Tree),作为一种重量平衡树,满足树高稳定,支持大部分操作,并且可以高效维护可持久化,复制操作。

基本概念

\(sz_u\) 为 WBLT 上 \(u\) 子树中节点个数,\(ls_u,rs_u\)\(u\) 的左右儿子编号,\(val_u\) 为节点的权值。

特别的,因为是 leafy 的,\(val_u\) 只有在叶子节点有值,\(ls_u,rs_u\) 只有非叶节点有值,要么同时有值。

维护平衡

WBLT 维持平衡的关键在于其满足 \(\alpha\) 平衡,即 \(\frac {\min (sz_{ls_u},sz_{rs_u})} {sz_u} \le \alpha\),这保证了其树高在 \(\log_* n\) 的量级。

然后考虑怎么快速维护平衡,这里是 WBLT 参考的一份 merge 函数的代码:

inline pair<int, int> cut(int u) { return {t[u].l, t[u].r}; }
inline int join(int u, int v) {  int x = newn(); t[x].l = u, t[x].r = v; return x; }
inline bool ck(int x, int y) { return x >= ALPHA * (x + y); }
inline int merge(int l, int r) {
    if(!l || !r) return l | r;
    if(ck(t[l].sz, t[r].sz)) {
        auto [a, b] = cut(l);
        if(ck(t[b].sz + t[r].sz, t[a].sz)) {
            auto [c, d] = cut(b);
            return merge(merge(a, c), merge(d, r));
        }
        return merge(a, merge(b, r));
    }
    if(ck(t[r].sz, t[l].sz)) {
        auto [a, b] = cut(r);
        if(ck(t[l].sz + t[a].sz, t[b].sz)) {
            auto [c, d] = cut(a);
            return merge(merge(l, c), merge(d, b));
        }
        return merge(merge(l, a), b);
    }
    return join(l, r);
}

假如 \(l,r\) 直接合并满足平衡,那么可以新建一个 leafy 节点将 \(l,r\) 作为它的左右儿子即可。

否则我们不妨设 \(sz_l \ge sz_r\),先考虑把 \(l\) 分裂成左右两个儿子 \(a,b\),然后考虑合并 \(b\)\(r\) 成一个点再与 \(a\) 合并,但是有可能 \(b\)\(r\) 合并与 \(a\) 不满足平衡,需要将 \(b\) 也分裂成 \(c,d\) 然后再分别合并 \((a,c),(d,r)\),最后合并到一起。

过程中最复杂/最坏的就是要经过两次分裂的情况,把这个树的情况画出来我们实际做的其实是个双旋,想要这样做是正确的,对于 \(\alpha\) 的取值是有要求的,需要保证在二次分裂/双旋的情况下能保证维护平衡性。

对于 \(\alpha\) 在以上合并中的限制,我们不难列出若干关于 \(\alpha\) 的低次不等式,在此处不过多展开,可以得到 可行的 \(\alpha \in (0,1-\frac {\sqrt 2} 2]\),通常我们取 \(\alpha = \frac 1 4\),可以避免浮点数的运算,并且效率也比较优秀。

特别的,对于 WBLT 的 merge 函数,合并两颗树 \(x,y\) 的复杂度是严格 $O(\log {\frac {sz_x} {sz_y}}) $ 的。\((sz_x \ge sz_y)\)

可持久化,复制

基本的平衡树操作与普通的 leafy 平衡树没什么区别,此处不多赘述。

考虑可持久化,对于 WBLT 而言是非常方便的,我们考虑通过合并维护平衡的写法,只需要每次 join 的时候新建点即可,对于有修改的情况,我们每次把修改影响到的点复制一份即可,对于有 lazytag 的情况,每次下放的时候把儿子都复制一份新的即可。

P8263

https://www.luogu.com.cn/problem/P8263

WBLT 维护序列区间复制的模板。

考虑复制,可以先把要分裂的区间取出来,倍增构造出其复制 \(2^i\) 倍后的树,这是 \(O(\log k)\) 的。

然后我们要取若干个其中的树合并,因为单次复杂度是 $O(\log {\frac {sz_x} {sz_y}}) $,所以我们总的合并代价 \(\sum \log {\frac {sz_x} {sz_y}} \leq \log k\),发现复制部分(抛开分裂的部分)的复杂度是与原树无关的 \(O(\log k)\)

题目中操作 2 那个花里胡哨的复制也可以用简单的倍增维护,在复制的时候翻转一下就行了。

程序校验 I

UOJ 960,luogu P11692

会了后 WBLT 可以考虑一个暴力点的做法。

考虑有效的取模的次数是 \(\log V\) 级别的,我们可以考虑扫描线 \(l\),维护一颗平衡树,第 \(i\) 个位置记录一个数 \(x=i\)\(l\) 开始首次有效的取模函数是哪个。

然后考虑从 \(i+1\) 的平衡树变到 \(i\),发现实际上是对 \(x\notin [l_i,r_i]\) 以及 \([l_i,l_i+a_i)\) 的部分复制 \(i+1\) 的平衡树,然后对于 \([l_i+a_i,r_i]\) 的部分全部打上 \(i\) 的 tag。

注意 \(l_i+a_i > r_i\) 的情况以及一些其余的实现细节,复杂度是 \(O(n\log V + m\log^2 V)\)

DAG 剖分的 1log 做法先鸽了。

但是因为这里的 2log 是很容易被卡满的,所以需要优化。考虑类似全局平衡二叉树的思路,原本的做法并没有利用到每次取模后值域也会折半的性质,而是每次在 \(\log V\) 颗平衡树上都跳了 \(\log V\) 次。考虑优化,我们把所有信息维护到一颗树上,把原本的平衡树上的边分为红边和黑边,红边代表这里是一次取模,并在边上记录权值,对于红边我们不再是单独跳到另外一颗平衡树上,而是直接把另一颗平衡树的对应子树复制过来。

对于一颗 WBLT,因为满足重量平衡,所以黑边是 \(\log V\) 量级的,对于红边有取模的性质,也是 \(\log V\) 量级的,所以单次询问复杂度 \(O(\log V)\)

具体实现参考了别人的代码,一种比较简单的写法是对于红边我们可以用一个只有左儿子的非叶节点表示,并在结点上记录取模的数,在 WBLT 的 merge 里我们对于这样的节点不进行分裂。复杂度分析跟原本是类似的。

posted @ 2026-05-07 16:09  yzq_yzq  阅读(19)  评论(0)    收藏  举报