[轉]我的數據結構不可能這么可愛!——珂朵莉樹(ODT)詳解


參考資料:

Chtholly Tree (珂朵莉樹) (應某毒瘤要求,刪除鏈接,需要者自行去Bilibili搜索)

毒瘤數據結構之珂朵莉樹


全是珂學家的珂谷,你卻不知道珂朵莉樹?來跟詩乃一起學習珂朵莉樹丫~

(挑戰用最短的篇幅講清楚一個毒瘤數據結構)

1、珂朵莉是什么?

珂朵莉·諾塔·瑟尼歐里斯是輕小說及改編動畫《末日時在做什么?有沒有空?可以來拯救嗎?》中的女主角,五位成體妖精兵之一。最強聖劍“瑟尼歐里斯”的適合者。在第28號浮游島上意外跌落而與威廉相遇,並受到他的幫助。 但是知道了珂朵莉對你學習珂朵莉樹並沒有什么幫助。

2、珂朵莉樹是什么?

珂朵莉樹又稱老司機樹(Old Driver Tree),可能是因為發明者是一個珂學家所以發明了這個數據結構?


廢話到此結束。

珂朵莉樹是基於C++STL庫中的set的數據結構。與線段樹、平衡樹等樹形結構類似,珂朵莉樹是用來解決區間問題的很暴力的樹形結構。

特色:珂朵莉樹支持區間推平(將區間$L$~$R$全部賦值為同一個值$x$)

3、在學習之前你需要知道:set(若熟悉此STL請跳過)

set的本質是一棵平衡樹。特性為插入到set中的元素會被自動排序並且不允許set中有相同的元素。

珂朵莉樹中需要用到的函數:

set <Node> t; //定義一個名為t,類型為Node(結構體)的set s.begin(); //返回指向第一個元素的迭代器(地址) s.end(); //返回指向最后一個元素的迭代器 s.clear(); //清空s s.insert(a); //將a插入到s s.erase(l, r);//將迭代器l~r之間的元素全部刪除 s.lower_bound(a); //二分查找a在s中的位置,返回一個迭代器。 set<Node>::iterator pos; //定義一個迭代器pos。

 

4、珂朵莉樹可以解決什么問題?

當我們需要推平一段區間時,可以使用珂朵莉樹。珂朵莉樹高效的基礎是數據隨機生成。也就是說,這個數據結構很容易被構造數據卡掉。但是在珂朵莉樹依然鮮為人知的現在又有誰會去構造數據去卡一個知名度並不高的算法呢?你都願意學SPFA了不是嗎

來看一道例題:

CF869C Willem, Chtholly and Seniorious

題目大意:

我有一個可愛的序列。你需要編寫程序支持以下操作:

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 :輸出[l,r] 區間每個數字的$x$次方的和模y的值

5、珂朵莉樹的構造

其實珂朵莉樹被稱作“樹”大概僅僅是因為使用了set吧。在學習珂朵莉樹時,你大可以不把它作為一個樹來理解。

珂朵莉樹的每一個節點由一個三元組(l, r, val)組成,表示區間$[l,r]$之間的值全是x。

例子: 我有一個可愛的序列:

1 1 1 1 1 2 2 2 2 3 3 3 3

則在珂朵莉樹上這個序列被表示為(1, 5, 1) (6, 9, 2) (10, 14, 3)三個節點。

建樹實現:

struct node { int l, r;//區間左端點與右端點。 mutable lint v;//mutable為“可變的”,使我們可以直接修改v的值 node(int L, int R = -1, lint V = 0) : l(L), r(R), v(V) {} bool operator < (const node &o) const { return l < o.l; } }; //定義一個結構體,重載<為按左端點排序。 set <node> s;//扔到set里

 

是不是非常的簡單好懂鴨?

6、核心操作:區間分割

考慮剛剛那個可愛的序列:

1 1 1 1 1 2 2 2 2 3 3 3 3

現在我要強人鎖男將2~13這個區間全部加上1。

前文提到,在珂朵莉樹上這個序列被表示為(1, 5, 1) (6, 9, 2) (10, 14, 3)三個節點。也就是說我們根本沒有辦法直接對2~13這個區間加上1.

這個時候如果我們將(1, 5, 1) (6, 9, 2) (10, 14, 3)三個區間分割為(1, 1, 1) (2, 5, 1) (6, 9, 2) (9, 13, 3) (14, 14, 3)這幾個區間就可以直接對區間2~13中所對應的節點直接進行操作啦!超快樂!

∴現在我們需要寫一個函數spilit(pos),將pos所在的節點以pos為中心分為兩個節點。

實現步驟:

  1. 找到pos所在的節點(l, r, val)
  2. 刪掉這個節點
  3. 把這個節點變成(l, pos-1, val)和(pos, r, val)扔回set。
  4. 返回右區間迭代器(以后有用)

代碼實現:

#define IT set<node>::iterator //迭代器宏定義 IT spilit (int pos) { IT it = s.lower_bound(node(pos, -1, 0));//找到第一個l不小於pos的節點 if(it != s.end() && it->l == pos) return it;//不需要分割直接退出 it--;//pos一定在前一個區間中 int L = it -> l, R = it -> r; lint V = it->v; s.erase(it);//刪了 s.insert(node(L, pos-1, V));//左區間丟進去 return s.insert(node(pos, R, V)).first;//右區間丟進去,返回右區間的迭代器。 }

 

是不是特別簡單?

7、降低復雜度的關鍵:區間賦值(推平)

實現步驟: 設要推平的區間為[l, r]。

  1. 把l和r所在的節點分割。
  2. 把要推平的區間[l, r]之間的節點全部刪掉。
  3. 把(l, r, val)扔進set

沒了。詩乃覺得沒有什么需要解釋的了。

#define IT set<node>::iterator void tp(int l, int r, int val) { IT il = spilit(l), ir = spilit(r+1); s.erase(il, ir); s.insert(node(l, r, val)); }

 

8、當我們要對一段區間進行操作時:

設要操作的區間為[l, r]。

我們先把l和r所在的節點分割,暴力對區間[l, r]中的節點一個個取出來操作(修改或統計答案)即可,反正沒有多少節點。這個東西視具體題目而定,這里不再贅述。

例子:區間加

#define IT set<node>::iterator void add(int l, int r, int val) { IT il = spilit(l), ir = spilit(r+1); for(; il != ir; il->v += val, il++); }

 

 

9、我最喜歡暴力數據結構了。

驚不驚喜?現在你已經學會珂朵莉樹了,可以做一做例題練一下手呢。(是NOI+/CTSC難度的黑題哦)。

其他例題: CF915E 、bzoj4293(權限題)

珂朵莉樹的優點:碼量少,好理解,調錯快。

缺點:在數據隨機的時候推平操作比較多,所以它的復雜度會趨近於mlogn(m為詢問次數)。但當出題人想要卡珂朵莉樹時肯定會T飛。珂朵莉這么可愛誰會卡呢

10、我永遠喜歡珂朵莉!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM