树套树
树套树
简介
简单来说就是两个树形数据结构的嵌套,一般是值域套区间,或者区间套区间(二维区间)。
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
进行命名空间的分隔以防变量名冲突,并使代码更加模块化,简洁化。
实现技巧二
由于内层树是动态开点,所以外层树实际上记录了内层树的树根。
并且在大多数问题中,合并内层树的代价非常大,所以需要使用标记永久化的技巧。
对于标记永久化,由于其不会合并两个儿子,所以在修改时需要将访问到的所有区间都做上修改。
在查询时也要将查询路径上的所有节点的答案贡献进去。
最后附上代码,略微压行,部分代码可读性不高。