华师一附中OI组

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1090|回复: 0
打印 上一主题 下一主题

主席树

[复制链接]

738

主题

1485

帖子

5422

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5422
跳转到指定楼层
楼主
发表于 2020-2-19 10:44:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
主席树

主席树就是利用函数式编程的思想来使线段树支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗的增强版的线段树。

很多问题如果用线段树处理的话需要采用离线思想,若用主席树则可直接在线处理。故很多时候离线线段树求解可以转化为在线主席树求解。注意,主席树本质就是线段树,变化就在其实现可持久化,后一刻可以参考前一刻的副本,二者共同部分很多。一颗线段树的节点维护的是当前节点对应区间的信息,倘若每次区间都不一样,就会给处理带来一些困难。有时可以直接细分区间然后合并,此种情况线段树可以直接敲定;但有时无法通过直接划分区间来求解,如频繁询问区间第k小元素,当然,此问题有比较特殊的数据结构-划分树。关于划分树,可以参考我的博客:划分树介绍。此时,除了划分树之外,主席树也是可以解决的。

主席树的每个节点对应一颗线段树,此处有点抽象。在我们的印象中,每个线段树的节点维护的树左右子树下标以及当前节点对应区间的信息(信息视具体问题定)。对于一个待处理的序列a[1]、a[2]…a[n],有n个前缀。每个前缀可以看做一棵线段树,共有n课线段树;若不采用可持久化结构,带来的严重后果就是会MLE,即对内存来说很难承受。根据可持久化数据结构的定义,由于相邻线段树即前缀的公共部分很多,可以充分利用,达到优化目的,同时每棵线段树还是保留所有的叶节点只是较之前共用了很多共用节点。主席树很重要的操作就是如何寻找公用的节点信息,这些可能可能出现在根节点也可能出现在叶节点。

下面摘自牛人理解:所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。

主席树的很多很好的性质取决于操作的方便高效。下面依次介绍主席树的更新、查询等操作,给出效率分析。

1、建立

首先建立一棵空的线段树,也是最原始的主席树,此时主席树只含一个空节点,然后依次对原序列按某种顺序更新,即将原序列加入到对应位置。此过程时间复杂度为O(M),空间复杂度为O(M*log(M))。

2、更新

更新时按照自己的离散值寻找对应位置,信息域data+1。我们知道,更新一个叶节点只会影响根节点到该叶节点的一条路径,故只需修改该路径上的信息域data。每个主席树的节点即每棵线段树的结构完全相同,只是对应信息域data不同(可以理解为线段树的结构完全一样,只是对应叶子节点取值不同,从而有些节点的信息域data不同,本质是节点节点不同),此时可以利用历史版本,即利用相邻的上一棵线段树的信息。相邻两颗线段树只有当前待处理的元素不同,其余位置完全一样。因此,如果待处理的元素进入线段树的左子树的话,右子树是完全一样的,可以共用,即直接让当前线段树节点的右子树指针指向相邻的上一棵线段树的右子树;若进入右子树,情况可以类比。此过程容易推出时间复杂度为O(log(M)),空间复杂度为O(log(M))。

3.查询

由于主席树每个节点是棵线段树,信息域、结构相同,可以相减。这是主席树查找的关键所在。例如查找第k小的元素,若左子树信息域data之差大于等于k,则直接到左子树查找;否则调整k值即减去左子树的信息域data之差,然后到相应的右子树查找。由于是线段树属于二叉树结构,故整个过程的时间复杂度为O(log(M)),M往往是原问题离散化后的数据数量级。对于任意主席树的节点即某棵线段树,其含义再次说明一下,存储的是原序列的某个前缀:a[1]、a[2]…a[k],其中k小于等于M,所以主席树节点i、j信息域data相减得到的即为原序列在区间[i,j]上的信息域data。此过程时间复杂度为O(log(M)),。

总结:

由以上可知,主席树是一种特殊的线段树集合,属于可持久化线段树,具备线段树的几乎所有优势。此处可持久化我的理解就是可以充分利用历史版本,从而达到优化目的,故主席树更新、查找的时间复杂度为O(log(M)),且总的空间复杂度为O(M*log(M)),这些对于复杂问题都是可以接受的。关于主席树的代码,结合具体题目会在后面的有所展示。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|服务支持:DZ动力|华师一附中OI组  

GMT+8, 2024-12-26 02:35 , Processed in 0.096938 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表