ODT

ODT,珂朵莉树(Chtholly Tree),又称老司机树(Old Driver Tree, ODT),最初出现于 lxl 出的 CF896C 中。

CF896C

给定一个数列,支持四个操作:

  1. l r x 区间 \([l,r]\)\(x\)
  2. l r x 区间 \([l,r]\) 推平为 \(x\)
  3. l r x 查询区间 \([l,r]\)\(x\) 大数。
  4. 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 虽然每个平凡操作基本都是暴力,但其在随机数据下的表现是十分优秀的,但是大部分情况会被卡掉,所以当个暴力写还是很不错的,万一跑过了呢。

Submission

posted @ 2025-06-03 16:44  Wei_Han  阅读(23)  评论(0)    收藏  举报