本文同步发表于 博客园。有问题请不吝指出。
ODT (Old Driver Tree)也叫珂朵莉树(Chtholly Tree),也被叫做是颜色段均摊,由某毒瘤巨佬在 CF896C 中引入。
ODT 是一种暴力数据结构,感觉和 dsu on tree、势能线段树什么的都比较类似,类似暴力的优化。很牛,是不是?
ODT 的核心思想就类似 将值(颜色)相同的一段区间合并为一个元素。
其有一些使用场景:
- 区间赋值,区间查询——可以使用
map维护相同颜色段以及段信息。
先看个题,然后从中来学习 ODT。
P1840
黄题。
这个题显然有其他方法来做,不过这里就将 ODT 的做法。
题意:在一条数轴上有 \(n\) 个点,分别是 \(1,2,\ldots,n\)。一开始所有的点都被染成黑色。接着我们进行 \(m\) 次操作,第 \(i\) 次操作将 \([l_i,r_i]\) 这些点染成白色。请输出每个操作执行后剩余黑色点的个数。
显然,注意到每次染色后数轴上的点都是黑白相间的。
原本最朴素的暴力是不能通过的,所以我们考虑利用上面的颜色段的性质来对暴力进行优化,也就是减少一些暴力。
假设我们现在要对数轴的红色区间里面进行操作:

(由于作者太懒就使用 s 老师的图了)
我们的朴素暴力显然就是在这个区间里面暴力修改。但是实际上并不用这样——我们要开始优化暴力了。
由于颜色是成段的,所以我们发现,对于每一个段,只需要修改这个段的信息即可。
存储颜色段
如果上面的看不懂,可以继续看下面的更加详细的解释。
我们现在只谈修改(也就是说,我们暂时先不考虑查询),看我们如何去实现一下这个暴力。我们可以简单地使用 \((l,w)\) 来表示颜色段信息,\(l\) 为左端点,\(w\) 为值(注意这里的值和颜色不是一个东西,一个颜色段可以维护很多类型的东西)。
例如:

因为数轴上每一个点要么是黑要么是白,所以这两个表示方式是可以互化的。即如果应用第二个表示方式,我们照样也可以轻松求出区间的右端点。
注意,这里的最右边还有一个值为 \(-1\) 的东西。这个是为了防止边界溢出,因为左边的 \((38,1)\) 颜色段如果不加以限制的话,我们的算法很有可能把它当作是向右无限延长的。
那么这个东西 \((l,w)\) 该如何记录呢?有一个简单好写的方式就是直接使用 map 来维护。
修改
那么我们该怎么应对修改呢?
例如这样:

对区间中的所有数都变成 \(0\)。
在操作后,显然区间中的都变成 \(0\) 了,这会导致中间成为一个连续段。而 \([8,19],[38,45]\) 中只有一部分被波及,所以我们不得不修改一下某些颜色段的信息。

简单模拟一下,大概就能想出来如何应对修改了。
第一步:先把新的颜色段插入进去。

也就是直接二分 \(L,R\) 即可。
第二步:再把中间的颜色段删掉。

这个好像真没办法优化。暴力删除。
第三步:把新的颜色段的权值修改成我们想要的值。
就是直接改。
这样,我们的修改就完成了。
考虑分析一下修改的复杂度。答案是 \(O(q \log n)\),\(q\) 为询问个数。为什么呢?
因为,每次操作最多会生成两个结点,然后这些生成的结点也只会被删除一次并在后续访问一次。
而二分和修改 map 都是 \(O(\log n)\),所以最终就是 \(O(q \log n)\)。
这意味着,在任何的数据下(可以不是随机数据!),不带查询的 ODT 的速度都极快。这就是暴力数据结构的厉害之处!!
回到 P1840,这个题剩下的就不多了。对于查询黑色点的总数量,每次修改时都动态维护其即可。
简单总结
根据上文对修改和存储的分析,我们可以发现:ODT 实际上真的没啥东西,本质上,就是使用 set/map 来维护颜色段的区间信息,然后其修改和查询就都是在上面做暴力得出的。
个人感觉,暴力数据结构没有改变其朴素、“野蛮”的本质,但是却对各方面效率有了极大的提升。这也许就是暴力数据结构的最大妙处吧。
查询
有修改但没有查询的就是好的 ODT,那是不是意味着有查询的就是不好的 ODT 呢?
容易想到查询的方法。
查询就是再往数组里面塞查询区间的左右两个端点,然后把里面的所有颜色段的权值加起来即可。(注意这里的加是广义的,也就是说可以不是两个数加起来,还可以是两个二元组加起来,类似合并)

这个复杂度有点高。但是好像也确实是我们目前可以想到的最好的暴力方法。
这个东西单次复杂度就已经达到了 \(O(\min(q,n) \log n)\) 的级别。好像随便精心构造一下数据都能卡掉……
现在我们发现,如果 ODT 没有查询,那它每次就是 \(\log\) 的,还是一个不错的算法,还算优美,还可以解决很多问题。
但是一碰到多次查询,它就会原地爆炸了……
——等等?我们前面说的能把 ODT 的区间查询卡掉的前提情况,是出题人精心构造了数据。那么出题人不用脑子造数据,或者是数据随机的情况呢?
如果数据是随机的,而且你使用了 map/set 来实现,那么区间查询的理论值是 \(O(q \log \log n)\) 的。
我不会证,只能给个 链接 跑路了。