ODT
ODT,珂朵莉树(Chtholly Tree),又称老司机树(Old Driver Tree, ODT),最初出现于 lxl 出的 CF896C 中。
CF896C
给定一个数列,支持四个操作:
l r x区间 \([l,r]\) 加 \(x\)。l r x区间 \([l,r]\) 推平为 \(x\)。l r x查询区间 \([l,r]\) 第 \(x\) 大数。l r x y查询 \(\sum \limits_{i=l}^r a^x \pmod y\)。
保证数据随机,\(1 \le n,m \le 10^5\)。
ODT 的复杂度正确性是基于操作随机的,或者说有大量且平均的区间推平操作,使用 ODT 可以做到亚于 \(O(n\log n)\) 的优秀复杂度。
ODT 是基于维护颜色连续段的一种数据结构,一般使用 set 实现,当然鉴于其每次是轮流访问迭代器,可以使用链表做到小常数实现,但码量相应的更大。
逐个操作来讲:
分裂(spilt)
ODT 的重要操作之一,是将 \(\text{pos}\) 所在的连续段 \([l,r]\) 拆分为 \([l,pos)\) 与 \([pos,r]\),实现步骤很简单,首先 lower_bound 找到所在连续段,特判特殊情况,直接 erase 就好了。
具体使用时,应写 set<node>::IT R=spilt(r+1),L=spilt(l);,也就是拆分右端点在前,拆分右端点在后,具体原因是 erase 操作会使迭代器失效,而 insert 操作不会,而 spilt(l) 时不会访问到 \(r+1\) 所在的连续块,而反过来则可能会导致迭代器失效。
inline set<node>::IT spilt(ll pos)
{
set<node>::IT it=st.lbd(node(pos));
if(it!=st.end()&&it->l==pos) return it;it--;
if(it->r<pos) return st.end();
ll l=it->l,r=it->r,v=it->v;st.erase(it);
st.insert(node(l,pos-1,v));
return st.insert(node(pos,r,v)).fi;
}
合并(assign)
将 \([l,r]\) 区间覆盖为 \(x\),具体实现将 \(l,r\) 分别拆分出来然后删除一段迭代器区间再插入即可。
inline void assign(ll l,ll r,ll x)
{
set<node>::IT R=spilt(r+1),L=spilt(l);
st.erase(L,R);st.insert(node(l,r,x));
}
其余操作就是暴力做了,因为保证了数据随机,所以这样做复杂度是正确的。
举个例子,求区间 kth:
inline ll rnk(ll l,ll r,ll x)
{
set<node>::IT itr=spilt(r+1),itl=spilt(l);
vector<Rank> P;
for(set<node>::IT i=itl;i!=itr;++i)
P.pb(Rank(i->v,i->r-i->l+1));
sort(all(P));
for(auto [num,cnt]:P)
if(cnt<x) x-=cnt;
else return num;
}
ODT 虽然每个平凡操作基本都是暴力,但其在随机数据下的表现是十分优秀的,但是大部分情况会被卡掉,所以当个暴力写还是很不错的,万一跑过了呢。

浙公网安备 33010602011771号