树套树

树套树

简介

简单来说就是两个树形数据结构的嵌套,一般是值域套区间,或者区间套区间(二维区间)。

P3380 【模板】树套树

看到查询排名与第 \(k\) 大会想到主席树,但其无法支持修改。

所以考虑树套树,外层用棵线段树表示区间,内层用一棵权值线段树表示值域。

考虑如何实现操作二,尝试二分,时间复杂度来到 \(O(log_n^3)\)。不太可取。

经典技巧一

考虑如果只涉及一棵权值线段树,可以直接在线段树上二分。可以类比主席树,其是两棵树一起走。

看目前的做法,其会涉及 \(log_n\) 棵树,只需要让这 \(log_n\) 棵树一起走就好了,时间复杂度优化到 \(O(log_n^2)\)

然而这是假的

因为我们还要求前驱和后继,所以需要使用平衡树而不能用权值线段树。

如果继续以平衡树作为内层树,那么时间复杂度仍然为 \(O(n log_n^3)\)。不可取。

经典技巧二

我们尝试以权值线段树表示值域作为外层树,以平衡树表示位置作为内层树。

对于操作一,直接统计 \([0,k)\) 中位置在 \([l,r]\) 中的个数即可。

对于操作二,在权值线段树上二分。

对于操作三,先删点,再加点。

对于操作四,询问排名,再询问排名为其排名减一的元素。

对于操作五,类似于操作四。

做到这里,发现其实不一定要以平衡树作为内层树,但是因为它的常数和空间相比于权值线段树都更加优秀,所以仍然选择平衡树。

到这里就讲完了,总的时间复杂度为 \(O(n \log_n^2)\)

对于此题而言,需要注意的是,一个元素可能重复,所以其排名等于小于它的数的个数加一。

实现技巧一

对于树套树,由于空间的限制,内层树一般需要动态开点。对于外层树为了实现方便与不引起混乱最好选择建出完整的树。

可以选择 namespace 进行命名空间的分隔以防变量名冲突,并使代码更加模块化,简洁化。

实现技巧二

由于内层树是动态开点,所以外层树实际上记录了内层树的树根。

并且在大多数问题中,合并内层树的代价非常大,所以需要使用标记永久化的技巧。

对于标记永久化,由于其不会合并两个儿子,所以在修改时需要将访问到的所有区间都做上修改。

在查询时也要将查询路径上的所有节点的答案贡献进去。

最后附上代码,略微压行,部分代码可读性不高。

代码链接

posted @ 2024-05-16 22:12  DeepSeaSpray  阅读(5)  评论(0编辑  收藏  举报