发现很多数据结构我都是只写过一两次根本不熟练,有些东西的某些应用都不太了解。
感觉药丸,反正先挖个坑。
UPD 2016.11.23 动态开点线段树及其合并
这里居然有个坑。
然后我是不是奶了一下,然后我在NOIp中写数据结构就挂了。
先来干一发动态开点线段树及其合并,讲一讲基本操作。
思路很简单,就是假装有一个下标范围非常大(通常是权值范围1...V)的线段树,原先的结点数量非常多,那就考虑只建出对应信息非0的结点。
当然根据线段树的性质,层数上限还是logV,然后可以发现V变得很大时logV基本不变。
那么建树建个根结点就好了O(1),修改如果进入未建出的结点才新建O(logV),比较优秀。
还有就是合并,两个子树合并如果一者为空则取另一者,均不为空则递归左右子树。
考虑合并树a和树b复杂度,我们发现这取决于递归到最优子树的次数。
显然我们只会在访问到a和b的公共结点时递归,于是合并复杂度为O(min(a.size,b.size))。
这类似于启发式合并,容易知道,将若干个size和不超过n的树,以任意给定顺序合并到一起,时间复杂度不会超过O(nlogn)。
于是我们可以支持这样的一些操作:维护若干个集合,支持加数,合并,求第k大,求排名。建成权值线段树就好了。
然后我感觉如果这篇博文全部贴源代码的话,就会非常长然后就比较难看,所以我扔了Ubuntu Pastebin。
然后我写的时候,加数我是写成建一个单个数的集合然后再合并进去。
代码:http://paste.ubuntu.com/23521308/
然后写完数据结构肯定要套几个题目。
bzoj2733 永无乡
裸题,直接拉板就好了。
代码:http://paste.ubuntu.com/23521359/
CodeVS fib
动态开点线段树套矩阵乘法。
http://paste.ubuntu.com/23553158/
UPD 2016.11.25 树链剖分+线段树
我原来想把这种东西叫做树链剖分套线段树,后来想想并不能算作是“套”。
毕竟树套树一般都是指树的每个结点都对应着一棵树,然而在树链剖分+线段树里,我们一般都是只建一棵总线段树。
但是总的来说,这和树套树的共同点是,将两种不同的树形数据结构结合了。
然后树链剖分和线段树是非常经典的一种结合。
然后实现也很无脑,遍历的时候我们优先遍历每个结点的重儿子,为结点打上dfn序。
那么我们可以发现,一条重链上的各店的dfn序是连续的,一棵子树中的各点的dfn序也是连续的。
根据这个性质,我们先预处理一棵总线段树,按dfn序维护每个结点的信息以及每个关于dfn序的连续段的信息。
然后对于链操作我们就拆成log条重链扔进线段树复杂度O(log^2n),对于子树操作我们就直接扔进线段树复杂度O(logn)。
然后讲几个细节,都是我很久没写了于是没注意到的东西:
1、线段树建树时,线段树中第x个叶结点初始信息要对应原树中dfn为x的结点,而不是原树中编号为x的结点。
2、还是线段树的各种操作,信息上传信息更新标记下传标记合并什么的不要写错,因为难点肯定在于线段树,树链剖分往往只是个小套路。
bzoj2243
也就是线段树的操作稍微麻烦一点。
代码:http://paste.ubuntu.com/23531345/
UPD 2016.11.29 主席树
思路以前的博文有,大概一年前了。
bzoj3123
启发式合并,然后维护一下倍增数组和主席树。
然后那个testcase不是数据组数而是测试点编号!
然后那个testcase不是数据组数而是测试点编号!
然后那个testcase不是数据组数而是测试点编号!
不注意的话就可能会RE/TLE,所以我的代码里有一堆奇奇怪怪的调试信息。
代码:http://paste.ubuntu.com/23553179/
UPD 2016.12.3 虚树
通常会约定每次给出的m之和不会超过某个值,也即,你要给出一个每次时间复杂度没有因子n的算法(包含logn当然是允许的)。
既然不能包含n,即不能对整棵树进行dp,我们考虑只取下其中的若干“关键点”,然后将非关键的东西缩掉,得到一棵新的等价的树,就容易处理了。如图。
我们点集按dfs序排序,然后每两个相邻的点的lca和点集中的每个点都是“关键点”(要去重)。如果不取下lca,路径交叉处不好处理。
具体实现过程中,我们需要知道新树每个结点的父亲以及新树的dfs序,我们可以这样做:
考虑用一个栈维护这个新树中dfs序最大的从根向下的链,然后每次加入一个dfs值递增的点,考虑新点和链底端的点,如果新点不在链底端的点的子树中,那我们就可以不断弹出链底端的点,同时标记这些弹出的点的父亲结点就是链中的上一个结点。注意,最后一次弹出时,父亲结点应该设为新点和它的lca。如图。
于是直到新点在链底端的点的子树中,把新点加进去(如果至少弹出一次,而且最后一次弹出求得的lca还未被加入过链中,则应先将lca加入链底,然后加新点),维护完成。
最后还需要将栈中剩余元素弹出。同时注意到,如果将弹出的顺序反过来,则恰好是新树的dfs序。
有些时候这棵树可能并不需要真的建出来,如某些自下向上的树形dp,直接按照dfs序扫一次同时对父亲结点做贡献即可。
bzoj2286
建完虚树就好了,dp部分是人都会。
代码:http://paste.ubuntu.com/23571314/
bzoj3611
(未完待续)
UPD 2016.12.9 线段树时间分治
对于一些有加入元素和删除元素的离线问题,我们可以采用一些技巧将其转化。
考虑一个元素的加入时间戳和删除时间戳,将这个时间段扔到一棵以时间戳为下标建立的线段树上。
于是每个元素被挂在了线段树的某些结点上,每个询问被挂在了线段树的某个叶节点上,一个询问会受到该叶结点及其所有祖先结点上的元素的贡献。
如果一些元素对一个询问的贡献,在元素被分组的情况下可以支持贡献的快速合并,那么对每个询问枚举其每个祖先结点然后合并贡献即可,这样就转化为了不带修改的问题。
如果不具有上述性质,但是如果在只有加入元素而不删除元素的情况下数据结构是可撤销的,即度是每次严格而非均摊分析的,也有相应的处理方法。
考虑对这棵线段树进行遍历,要在访问到每个结点时维护该结点及其祖先上的所有元素构成的数据结构,那么只要在向下遍历时加入元素而回溯时做撤销就好了。
如果这些性质都不具备,则可能要考虑其他的转化方法。
bzoj4311
由于维护动态加点的凸包时,并不容易做到每次严格,因此不适用于第二种转化。
但是当点集被分组时,要找和给定点的点积最大的点,只需要对每组点分别维护一个凸包,询问时对每组点分别找一次最大然后取最优组即可,这适用于第一种转化。
代码:http://paste.ubuntu.com/23607057/