树套树,顾名思义,就是用一颗树套在另外一组树之上。比方说我们有一颗树,假如它的每一个结点(抽象意义上,一株树由多个结点组成)也是一株树,那么这就形成了树套树。外部树和内部树可以是不同品种的。一般外部树都是形如线段树或树状数组等用于统计和区间处理但是内存消耗大的树,而内部树则往往是像Splay一样功能强大且内存消耗小的树,当然这不是绝对的。
考虑这样的一个场景,给你一组数值序列S={v1,v2,....},要求你快速求区间[L,R]之间第k小的值,并且数值序列中可能有若干元素会被修改。如何解决?首先我们建立一株线段树,线段树内每个区间(对应原线段树结点)均用Splay树维护,区间[x,y]对应的Splay树用于保存子序列S[x...y]中的值。显然一次更新操作,我们只需要更新log2(n)个区间,对应的也就是log2(n)个区间,更新操作可以用删除原Splay结点并插入一个新的结点来实现,因此一次操作的空间复杂度为O(log2(n)),当然时间复杂度为O(log2(n)*log2(n))。而一次询问操作,我们只需要对可能的值做二分查找,因此问题就转化成了询问S[L...R]中小于某个特定值v的值数目。而具体的实现是,对于线段树我们仅查找区间[L,R]下对应的多棵Splay树,统计每一株树中小于v的结点数目,就可以得到正确结果。因此询问操作时间复杂度为O(log2(|V|))*log2(n)*log2(n))。其中V表示值域,由于值域较难控制,我们也可以选择用线段树维护值信息,而用Splay维护区间统计信息,这样时间复杂度就为O((log2(n))^3),但是这一般需要提前对数据(包括初始数据和后面修改的所有数据)进行预先离散化处理,相应的消耗的空间也会更大。
也许你会问,为什么要用Splay树作为内部树,Splay树在上面场景中不就是用于统计区间信息吗,为什么不直接使用线段树套线段树呢。这是因为在创建外部树时,我们就会创建内部结点,而如果内部结点是空Splay树,每个结点消耗的空间都是常数,如果是线段树,那么消耗的空间就是O(n),这样由于外部树有O(n)个内部树结点,而每棵内部树的空间复杂度都是O(n),那么空间复杂度将达到O(n^2)。当然你可以选择先建立一株空树,之后多株线段树在空树的基础上进行可持久化修改,这样初始化的空间复杂度与线段树套Splay树一致,但是每次后续修改操作其花费的空间复杂度为O(log2(n)*log2(n)),比线段树套Splay树的O(log2(n))要大。
树套树的玩法应该还有很多,以后遇到了再补上。
原文地址:https://www.cnblogs.com/dalt/p/8284640.html