前言
\(ODT\),即珂朵莉樹,又稱老司機樹(\(Old\ Driver\ Tree\))。
它是一個十分暴力的數據結構,可以用於各種亂搞,也非常的實用。
當然,這全要基於一個基本條件:數據隨機。
主要思想
\(ODT\)的主要思想就是把一個元素完全相同的區間合並成一個節點,然后用\(set\)維護(我也不知道為什么稱其為“樹”)。
而在數據隨機的情況下,節點的期望個數是很少的,因此復雜度也就比較低。
核心操作: \(Split\)操作
\(ODT\)的核心操作就是\(Split\)操作。
\(Split(x)\)的作用是分裂出一個以\(x\)為左端點的區間。
對於這個操作,我們先用\(set\)的\(lower\_bound\)函數,找到左端點不小於\(x\)的第一個區間。
如果此時找到的區間左端點已經為\(x\)了,則直接返回這個區間。
否則,我們就將迭代器移動到上一個位置,而這個區間才是我們要分裂的。
設這個區間為\([l,r]\)。
則我們應將它分裂成\([l,x-1]\)和\([x,r]\)兩部分。
所以我們先將\(l,r\)用變量存儲下來,然后在\(set\)中清除原來的區間,並將新的區間插入\(set\)。
然后\([x,r]\)這個區間就是我們所要找的,將其返回即可。
這個操作代碼如下:
I IT Sp(CI x)//分裂出一個以x為左端點的區間
{
IT t;if((t=S.lower_bound(Il(x)))!=S.end()&&t->l==x) return t;//如果此時找到的區間左端點已經為x了,則直接返回這個區間
RI l=(--t)->l,r=t->r,v=t->v;S.erase(t),S.insert(Il(l,x-1,v));//將迭代器移動到上一個位置,先將l,r用變量存儲下來,然后在set中清除原來的區間,並將新的區間插入set
return S.insert(Il(x,r,v)).first;//區間[x,r]就是我們所要找的,將其返回即可
}
推平操作:\(Assign\)操作
顯然,光有\(Split\)操作顯然會\(T\)飛。
所以我們就需要一個推平操作,把某段區間合並成一個節點。
則我們把這個區間的左端點,以及右端點的下一個位置提出,然后刪除它們之間的所有節點(包括左端點但不包括右端點的下一個位置),再把新的節點加入即可。
這個操作代碼如下:
I void Assign(CI x,CI y,CI v)//推平操作
{
IT tr=Sp(y+1),tl=Sp(x);S.erase(tl,tr),S.insert(Il(x,y,v));//把這個區間左端點及其之后、右端點下個位置之前的所有節點刪除,然后插入新的節點
}
其余操作
其余操作就沒什么好說的了,直接暴力掃一遍即可,真是不能再暴力了。
例題
下面給出一道例題:【BZOJ1858】[SCOI2010] 序列操作(ODT裸題)(正解是線段樹)。