介紹
在備賽 xcpc 時,其實除了資料結構以外,絕大部分常用的大綱知識都學習了,但資料結構確實是練得最多的,本文主要介紹一下個人是如何學習資料結構的。
資料結構概述
資料結構大概是很多人比較牴觸系統學習的東西,因為許多資料結構來說,光是板子就比其他領域的演算法長很多。比如線段樹,可能是很多人從資料結構新手前往資料結構殿堂遇到的一道很大阻礙。
當然了,學習資料結構也不只有資料結構題,資料結構往往可以去最佳化許多其他演算法,比如 \(dp\) 問題,\(2-SAT\) 最佳化建圖問題等等。
資料結構的考量
我個人覺得對於一個人的資料結構的水平體現為以下幾點:
-
碼力,是否有著良好的除錯程式碼能力,是否具備所想即可正確使用程式碼實現的能力。
-
抽象思維,對於資料結構來說,本質上就是維護資訊的東西,那麼維護什麼樣的資訊是需要你去思考的,如何把一個題抽象出資料結構,著重於分析維護資訊。
-
經驗,我認為學習資料結構離不開經驗,是需要大量地練習。大量的練習可以讓你積累許多經驗,使得第一點,你可以比較清楚怎樣的語法去實現是比較容易除錯和分析的,第二點則你可以反應出需要設計怎樣的資訊:最常見的例子,我們多維護字首最值之類的具有單調性的資訊可以實現樹上二分。
-
實現能力,許多人學習各類的資料結構喜歡停留在思維概念上。比如線段樹的大致思想知道,但並沒有去實現,對於資料結構來說,可能一個函式有各種不同的寫法,有著各種不同的優勢,就比如線段樹的查詢來說:分為了 需要臨時變數合併資訊/不需要臨時變數合併資訊,後者則可以更好地使用結構體過載運算子快速地書寫複雜的資訊合併。
-
知識儲備,資料結構離不開知識儲備,就比如很多人認為學習了線段樹以後,學習 \(ST\) 表、貓樹之類的資料結構是無用的。每一種資料結構都有它所擅長的優勢領域,當如果你在寫過大碼題,顯然一個更小的常數的資料結構可以使得你的程式碼更不容易被卡常。
-
理性思考,資料結構不同於其他演算法,必須理性思考問題:\(\log^2{n}\) 與 \(\log{n} \times \log{V}\) 看似都是雙 \(\log\),但它們的空間和時間常數遠遠不同,這個過程中,你不能想當然的大致時間複雜度而忽略二者的差異。比如樹套樹當中,你內層樹的值域並不離散化,那麼線段樹套值域線段樹的空間可能會遠遠超過 \(1G\),但離散化後可能才 \(400mb\)。
-
無他,惟手熟爾是資料結構的殿堂進步的捷徑。對於初學者來說,萬萬不可以太過依賴於模版,否則學習更深層的資料結構當中,會出現很多疑惑點,比如線段樹合併、可持久化線段樹、動態開點線段樹等等。當你如果能快速書寫一個線段樹的程式碼,那麼你可以從程式碼當中體會到資訊的維護過程,從而具備抽象思考的能力,當然線上賽追求速度可以考慮使用板子,學習的時候儘量多寫寫,因為寫法不止一種,每個人有最適合自己的寫法,並且寫多了踩坑多了,會學習到每種題的經驗。
資料結構學習的道路
學習資料結構離不開一條成熟的路線指引,對於不同需求的選手,有著不同的推薦指南。
我們需要清楚,常見的資料結構無非分為三個方向:序列上問題、樹上問題、圖上問題。
對於這三個方向的問題可以分別練習,並且有許許多多的技巧都是書上不會講到的。
-
STL 容器。雖然這東西是 C++ 的,但並不妨礙它類比到其他語言。首先最常見的就是:佇列、棧、堆。這三個無論如何都需要學習好它們的概念和特點,因為它們將會伴隨著你的整個資料結構生涯,需要特別熟練掌握。其次,學習雜湊表是很有必要的,很多人把 \(map\) 當雜湊表是完全不正確的,雜湊表是 \(key\) 是無序的,是存在雜湊衝突的,我們常常會使用手寫雜湊解決,這點可以參照 oiwiki,或者使用 \(pbds\) 的雜湊表,更不容易雜湊衝突且更快。關於內建的平衡樹,因語言不同,那麼在此不詳細討論,但一些基本概念比如 \(map\) 底層是紅黑樹,修改和查詢都帶 \(\log{n}\) 是需要知道的。
-
基於基本容器的資料結構。這類既算演算法,也算資料結構,常見的有單調棧、單調佇列。這是必須掌握的東西,並且概念學習明白。除此之外,例如懶刪除堆、對頂堆的技巧可視個人能力進行選擇性學習。
-
ST 表。這個我個人認為是很有必要學習的,相信每個人都會接觸到基礎演算法,那麼一定會學習 倍增與快速冪,在基於倍增理念的基礎上,學習 \(ST\) 表的成本是很低的,並且你這也將成為你資料結構歷程上第一個學習到的非常有用的靜態非可差分性問題的資料結構。當然了關於它的擴充:\(2\) 維 ST 表、尾部新增元素更新、分塊 ST 表、四毛子演算法之類的可視自身能力掌握。它也是解決 \(RMQ\) 問題的最常見手段之一,它的常數小,程式碼短,是屬於新手學習資料結構的一個很好的 跳板。
-
並查集。這個是屬於貫徹資料結構一生都必不可缺少的東西,關於它你需要把所有東西全部學完:按秩合併、啟發式合併、路徑壓縮、如何實現帶權資訊合併、可撤銷並查集、種類並查集(擴充套件域並查集)、可持久化並查集(選學)。其中可撤銷並查集是你通往高階資料結構之路一個必不可缺的東西,它體現了一類經典的技巧-----回滾思想,這類思想將伴隨著你遇見的問題增多而有更深的體會。
-
字典樹。這個沒啥好說的,是於字串當中好學又好寫的東西,同時學習 \(01\) Trie,為未來的位運算問題的題進行鋪墊,關於它的可持久化屬於選學,並不是很難,頻率較低,其餘技巧比如全域性 \(+1\) 這些,屬於 \(ynoi\) 類的題當中會遇到的了。
-
樹狀陣列。這是你邁向高階資料結構的入門,你需要把這個資料結構反覆學習,不斷學習。關於它,你要開始正式思考資料結構維護的是哪一維資訊,不同維需要進行怎樣的不同處理。當然了,在這當中,你也可以學習一些比較好用的技巧,比如基於雜湊表的動態開點樹狀陣列、樹狀陣列上倍增等等。對它的評價為:其實就是維護 動態字首資訊。
-
線段樹。大概是很多人會用一輩子的資料結構。首先學習關於樹的基本相關知識,瞭解線段樹是一棵完全二叉樹。其次理解線段樹如何維護區間資訊的,常見是如何實現的?不同的人有不同的實現風格,初學者可以先自行學習任意一種實現方式。學習基本的單修區查線段樹,建議反覆練習十遍左右。然後應該學習懶標記的線段樹,在這個過程中,建議做做一些板子題。然後關於線段樹的進階其實並不需要太過著急,可以先從動態開點的線段樹進行學習,然後線段樹分裂、合併、二分這個建議系統性學習,配合題目進行練習。
-
可持久化類資料結構。關於主席樹是必學的,不用想著被替換,有時候離線難寫的很,這東西學習成本低,如果前幾點學明白了,學這個很快。至於可持久化的其他的屬於選學,學習價效比並不高,可持久化的平衡樹藍橋杯倒是考過。
-
平衡樹與文藝平衡樹。我更願意把這二者分開,前者是維護關於權值類的序,後者則是下標的序。後者可以看做另一種線段樹,但能做的奇怪操作比線段樹更多,空間比線段樹更小。關於平衡樹,我絕大部分都學習過了,個人推薦學習 \(fhq\) 與 \(splay\) 即可,其餘的價效比並不高。
-
笛卡爾樹。如果你學習了平衡樹,建議把這個還是順帶學習,就一個單調棧建樹,化 \(RMQ\) 問題為樹上的 \(LCA\) 問題,很方便一道堆關於 \(max/min\) 之類的區間數量統計。
-
樹鏈剖分。重鏈剖分是比較有必要學習的,屬於很板子的東西,學懂了序列上的,這個東西自然而然就會了。長鏈剖分較冷門,一般用於最佳化 dp 的,屬於選學。虛實鏈剖分即為動態樹。
-
動態樹。學習 \(LCT\) 其實就夠了,\(GBT\)、\(Top Tree\)、\(ETT\) 屬於如果想要擴充學習再進行學習。動態樹其實碼量不大,思考起來常見的是基於容斥思考,容斥設計資訊,有餘力可以學習:動態 dp、動態 MST等等之類的資訊。
-
點分治/點分樹/樹上啟發式合併,做樹上大規模查詢問題會用到,常見的樹上啟發式合併比較好學,解決子樹類問題,順便建議學習一下 dfs 序的基本概念。點分治、點分樹解決大規模樹上路徑統計,專題練習即可。
-
貓樹。選學,是用來解決靜態分治問題,它的理念比較像 \(zkw\) 線段樹,可以選學。
-
分塊。分塊是很有必要學的,當你打算進階 \(ynoi\) 的一些大資料結構題,分塊是必不可少的一步,學習方式和線段樹類似,但有很多額外需要學習的:值域分塊、序列分塊套值域分塊等等。
-
莫隊。個人建議必學的關於查詢問題的妙手。要想學好莫隊,使得自身不會只寫板題,那麼一定要學習可撤銷的概念,學習:值域分塊、回滾莫隊、帶修莫隊、莫隊二次離線。普通莫隊用處較小,樹上莫隊其實常見的樹上問題解決工具太多了,價效比也低。
-
勢能 ds。這類問題比較特別需要單獨練習才行,其實題目也較少,主要為洛谷的 bzoj 上的題。如果想要學習更難的勢能線段樹的題,可以學習吉司機線段樹、KTT 之類的。
-
字串的自動機部分,ACAM 是很有必要學的。如果是想打 xcpc 的,可以把字尾系列的學一下,pam 也可以學一下。常見的其實可以轉化為樹上問題
-
關於計算幾何,李超樹和凸包可以都掌握,半平面交可以考慮選學。前兩個很有用,可以都學學,基本入門倒是不難。
-
虛樹。虛樹是一個比較好用的東西,其實學習難度不大,如果你有笛卡爾樹的建樹基礎的話,應該能快速掌握,主要是應用於加快樹上 dp 的。
-
樹套樹還是可以學學,歸併樹之類的都比較好用好寫。
-
其餘雜 ds,析合樹可以學學,解決排列問題可以的。
關於資料結構平臺練習
LeetCode 的話其實不太適合練習資料結構的碼工,但上面的資料結構專欄有不少題比較適合新手,新手可以自行練習。洛谷上有許多模版資料結構題,可以考慮先刷熟練模版題再考慮進行其餘題練習,其餘題其實可以考慮羅勇軍的 <<演算法競賽>> 這本書中的高階資料結構專欄課後習題進行選擇練習。關於 atcoder 的話,可以把 abc 的 G 題單獨拿出來,篩選資料結構的題,那些都是比較常見的應用練習。關於 cf,可以使用 ACD,在上面篩選 \(2000 \sim 2400\) 的資料結構題進行練習。
關於 cf 的 \(2800+\) 的資料結構題,大部分具有技巧性,對碼工的要求並不是很高,可以單獨作為知識點進行學習。對於洛谷的 \(Ynoi\) 專欄,可以選擇性練習,大部分比較需要具備卡常能力以及碼工。
關於資料結構還有許多一些技巧性的東西,比如離線掃描線、整體二分、cdq 分治之類的等等,都是有必要在學有餘力的時候進行學習。當知識儲備差不多的時候,可以考慮篩選 ccpc 各個省的省賽題中的 ds 題進行練習。當感覺能力足夠的情況下,可以做做 icpc 區域賽相關的 ds 題進行挑戰。
總結
資料結構的學習是比較枯燥的,需要靜下心來去學,本文也只是心得分享,並未標出每個知識點需要學習的對應題,可能後續會為每個知識點挑選合適的題型進行整理,輔助各位更好地學習,資料結構中有許許多多的技巧,比如如何線段樹只需要 \(O(n)\) 空間完成所有查詢、如何實現正難則反、可差性轉化等等,都是需要進行大量的練習和學習。
如果要學習基礎的資料結構教程,可以關注我的 b站賬號 Athanasy演算法,基礎資料結構教程將會持續更新,區別於其他教程,主要講解程式碼實現與細節,還有實用性。