# NYCU PCCA 2022 Winter Camp 0125 ## [Part 1. STL](https://www.youtube.com/watch?v=Pp4tgANoWC4&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=1) ### I. vector #### 1. 常用函式 ``` cpp= #include <vector> vector<int> v; v.size(); // v 的長度 v.resize(n); // 調整大小為 n v.clear(); // 初始化 v v.push_back(val); // 放 val 在 v 後面 v.begin(); v.end(); // v 的 iterator ``` #### 2. vector vs. array |vector|array| |------|-----| |可更動大小|不能更動大小| |宣告較複雜|宣告簡單| |功能多|很單純的陣列| #### 3. 其他函式說明 * `vector<int> v(n, 0);` >int: 陣列中要放的型別 v: 名字 n: 長度 0: 初始值(沒放的話則會是預設值) 二維陣列 ```cpp vector<vector<int>> v(n,vector<int> (n, 0)); ``` * `v.push_back(val);` 把 val 的值複製 * `v.emplace_back(val);` 把 val 做為參數來宣告 >兩者速度基本沒差 除非加入的東西很大 >**push_back() v.s. emplace_back()** emplace_back() 的好處在於它可以減少「宣告」的繁瑣(如下) ``` cpp= vector<pair<int, int>> arr; int main() { arr.push_back(make_pair(2, 5)); // 要先產生 pair 才可以被複製 arr.emplace_back(2, 5); // 簡潔許多 return 0; } ``` * `v.resize(n)` 把 v 長度設為 n * `v.resize(n, val)` 如果 v 需要增加長度,值會是 val 已存在的值不會被改變!!! * `v.assign(n, val)` 把 v 初始化成長度為 n 且值為 val * `v.reserve(n)` 在陣列後面預留 n 的空間 >v 的 size 仍不變 > >用來避免 push_back() 時,可能需要花 O(n) 的時間搬移 v 到連續記憶體更長的地方 * **請不要用 erase(), insert()** vector 是連續儲存的,所以移除或插入在中間會需要 O(n) 的時間 **" 這兩個東西是毒瘤啊! "** --- 所以現在是 iterator 時間 > " iterator 迭代器,簡單來說就是指標。 " * `v.begin()`: 回傳指向第 0 個的 iterator * `v.end()`: 回傳指向最後一項的下一項的 iterator 所以 v.begin() 到 v.end() 是左閉右開 * `v.rbegin()`: 回傳指向最後一項的 iterator * `v.rend()`: 回傳指向第 0 項的前一項的 iterator ``` cpp= #include <vector> vector<int> v(10); int main() { for (int &i: v) cin >> i; // 輸入 10 個東西到 v for (auto it = v.begin(); it != v.end(); ++it) { cout << *it << '\n'; // 記得加 * 在變數前面來取值 } } ``` ### II. tuple 適合==儲存一組資料的東西==(例如: x, y 座標) ```cpp= #include <tuple> #include <string> #include <vector> tuple<int, long long, string> t = {13, 53, "hello"}; pair<int, long long> p = {11, 42}; pair<int, vector<int> > pp; // 要存 vector 也可以 get<0>(t) // 回傳 t 的第 0 個東西 (13) p.first; p.second; // 分別回傳 p 的第 0 項跟第 1 項 ``` tuple 最多可以存 10 樣東西,但是較難使用 而 pair 是特化版的 tuple ,只能存兩種東西 宣告方式有以下兩種: ```cpp pair<int, long long> p = {11, 42}; make_pair(11, 42); ``` > 小技巧 > 使用 #define 讓 first, second 更短 ```cpp #define first f #define second s ``` ### III. algorithm ```cpp #include <algorithm> // 我都用萬用標頭檔哈哈 ``` * **sort()** " 你只要知道它的時間複雜度是 O(n log n),很快就對了。 " `sort(v.begin(), v.end());` 把 v.begin() 到 v.end() 之間排序 `sort(v.begin(), v.end(), cmp);` 依照自訂的 cmp 函式來排序 ```cpp= #include <algorithm> #include <vector> vector<int> v = {1, 3, 5, 2, 4}; bool cmp(const int &x, const int &y) { return x > y; } int main() { sort(v.begin(), v.end()); // 由小到大(預設) sort(v.begin(), v.end(), cmp); // 由大到小(利用 cmp 函式) sort(v.rbegin(), v.rend()); // 也是由大到小(倒著排序) } ``` * **lower_bound(), upper_bound()** 配合二分搜尋,時間複雜度 O(log n),特別注意==vector要先排好!== `lower_bound(v.begin(), v.end(), val);` 在範圍找出 ≥ val 中,最小的 iterator `upper_bound(v.begin(), v.end(), val);` 在範圍找出 > val 中,最小的 iterator * **nth_element()** `nth_element(v.begin(), v.begin() + val, v.end());` 時間複雜度 O(n),類似快速排序(?) 在範圍中,找出第 val 項 第 val 項之前的值都會 ≤ 第 val 項 第 val 項之後的值都會 ≥ 第 val 項 可以放 cmp * **next_permutation()** `next_permutation(v.begin(), v.end());` 時間複雜度 O(n / 2) 可以找出下一字典序大的排列 (prev_permutation() 可以找下一小的) 也可以放 cmp {1, 2, 3, 4, 5} -> {1, 2, 3, 5, 4} ### IV. set 沒錯,就是數學課教的集合 #### 1. 常用函式 ```cpp= #include <set> set<int> s; s.insert(val); // 在 s 中放入 val s.erase(val); // 把 val 從 s 中移除 s.clear(); // 清空 s s.size(); // s 的大小 s.find(val); // 找 val 的 iterator s.count(val); // 找 val 在 s 中的數量 s.begin(); s.end(); // iterator ``` #### 2. 特色 * **值不能重複** (multiset 支援插入重複的值) * **會自動排序** 可以快速取最大、最小值 (unordered_set 則不會排序) * **不能隨機取值** 就是不行用 s[2] 來找第 2 小的東西啦 * **可以用 iterator 迭代** #### 3. 說明 * `set<int> s;` * `set<int, cmp> sc;` 依照 cmp 去排序 但是跟普通宣告 cmp 的方式不太一樣… ```cpp= #include <set> struct cmp { bool operator() (const int &a, const int &b) const { return a > b; } }; set<int> s; // 由小到大排列 set<int, cmp> sc; // 由大到小排列 ``` * `s.insert(val);` 放 val 進 s 裡面 * `s.emplace(val);` 和 vector 一樣 * `s.erase(val);` 在 set 中移除值為 val 的東西 回傳被移除後的個數 val 也可以是 iterator 回傳被移除後的下一個 iterator(下一個比它大的) :::info erase() 的毒瘤操作 ```cpp= #include <set> set<int> s; int main() { s.emplace(1); s.emplace(2); s.emplace(3); for (auto it = s.begin(); it != s.end(); ) { if (*it == 2) it = s.erase(it); // 如果 it 指向 2,erase() 以後會回傳 3 的 iterator else it++; } } ``` ::: * `s.find(val);` 回傳 val 的 iterator 如果 ==val 不存在,則會回傳 s.end()== * `s.count(val);` 回傳 val 的個數 set 同樣的值只能有一個,所以==只會回傳 0 或 1== --- 所以現在是 iterator 時間 * s.begin() 指向第 0 項(最小值) * s.rbegin() 指向最後一項(最大值) 跟 vector 一樣,可以用 for 迴圈迭代 ### V. map " 跟字典一樣,問一個答一個。 " #### 1. 常用函式 ```cpp= #include <map> map<int, string> m; // <key, value> m[1] = "NYCU"; m[5] = "PCCA"; // 定義 m.clear(); // 清空 m.find(val); // 回傳 key = val 的 iterator m.count(val); // 回傳 key = val 的個數 m.begin(); m.end(); // iterator ``` #### 2. 特色 * **每個元素一個 pair** ({key, value}) * **key 不能重複** (multimap 支援 key 重複) * **會自動排序** (依據 key 進行排序(小 → 大)) unordered_map 則不會排序 ```cpp= #include <map> map<int, int> m; m[2] = 7; // 定義 m[4]++; // 如果索取的 key 未被定義,則會使用預設(0) // m[4] = 1 ``` #### 3. 其他函式 * `m.find();` 例如: m.find(2) 尋找 key = 2 的元素 回傳 pair {2, 7} * **iterator** ```cpp= #include <map> map<int, int> m; for (pair<const int, int> &i: m) { cout << "key: " << i.first << ' '; cout << "value: " << i.second << '\n'; } ``` ### VI. priority queue #### 1. 常用函式 ```cpp= #include <queue> // 它在queue裡面欸呵呵 priority_queue<int> pq; pq.empty(); // 是否空了 pq.size(); // 大小 pq.top(); // pq 中,值最大的 pq.pop(); // pq 中,移除值最大的 pq.push(val); // 把 val 放入 pq 中 ``` :::warning **跟 queue 的不同** queue 是 FIFO ( first in first out ) priority queue 不是用時間作為排序依據,而是以大小排序 ::: #### 2. 特色 * **預設是<font color="#f00">從大到小</font>排列!!!**(可以使用cmp) `priority_queue<T, vector<T>, less<T>>` T 表示資料型態 less<T> 表示資料由大到小排(預設) 改為使用 greater<T> 就會是由大到小排 你也可以寫 cmp ```cpp= struct cmp { // 由小到大 bool operator() (int &a, int &b) { return a > b; // 跟前面的反過來,有點毒瘤,要小心 } }; ``` ### VII. stringstream #### 1. 常用函式 ```cpp= #include <sstream> stringstream ss; int a, b; ss << "12 53"; // ss 寫入 "12 53" ss >> a >> b; // 從 ss 讀出,a = 12, b = 53 ss.clear(); // 清除 flag ss.str("PCCA"); // 把 ss 的內容設定為 "PCCA" ``` stringstream 宣告速度不快,所以==請避免重複宣告==,多資源回收~ * **寫入讀出** 寫入: ss << "Hello"; 讀出: ss >> s; 其實就跟 cin 、 cout 一樣啦 * **`ss.clear()`** 它只會初始化 ss 裡的 flag ,裡面的 buffer 不會清空!<font size = 5>不會清空!</font><font size = 6>不會清空!</font> 那我們要怎麼把它清空呢? 請使用下方的工具 str() * **`ss.str()`** `ss.str("PCCA");` 把 ss 的內容設成 “PCCA” `ss.str();` 回傳目前 ss 裡的字串 :::success **清空 stringstream 的方式** ``` cpp ss.str("") // 把它變成空字串 ss.clear() // 把一些不用的東西(例:EOF)清掉 ``` ::: --- ## [Part 2. DSU](https://https://www.youtube.com/watch?v=8dsEw2ndqCI&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=2) DSU = Disjoin-Set Union 有些集合,他們<font color="#f00">**互相交集為空集合**,</font>則這些集合稱為並查集,又稱互斥集。 ### 舉個例子: 學校裡有一些幫派,每個人只會屬於一個幫派,若某人==不加入別人,代表他自己就是一個單人幫派。== 幫派之間會互相鬥爭,==贏得一方可以把對方所有人納入自己的幫派==,也就是把兩個幫派合併成一個。 剛剛說的幫派是一群人組成的,我們只知道他是一個群體,但要怎麼表示這個群體最簡單呢? 我們可以從這個幫派之中,隨便挑一個人來當老大,==老大的作用就是用來代表這群體。== 我們可以透過不斷詢問老大的老大的老大的老大. . . 直到有人的老大是自己,就可以確定這個人是群體的代表。 ![](https://i.imgur.com/Md8NSd0.png) ▲ 框在一起的是同個幫派的成員;紅色底的是該幫派的老大。 #### 但這樣會比較快嗎? 最差狀況還是 O(n) 查詢 * parent[n] (boss[n]) 用來表示你的上層是誰,用 parent[i] == i 來偵測自己是否是最上層。 * size[n] 用來紀錄集合的大小,只有在 index 是最終老大時才有意義。 * 精簡版陣列 pa_sz[n] ``` cpp if (parent[i] != i) { pa_sz[i] = parent[i]; } else { pa_sz[i] = -size[i]; } ``` * find() find() 用來尋找最終老大。剛剛說過,一個一個找,最差情況會花費 O(n) 的時間。 ``` cpp int find(int index) { if(parent[index] == index) return index; return find(parent[index]); // 一路往上找 } ``` ![](https://i.imgur.com/uSLXc08.png) ▲ 假設今天1的幫派和5的幫派打架,5的幫派獲勝,那1的幫派就會被5的幫派併吞。 ![](https://i.imgur.com/Fmil7Z1.png) ▲ 而我們就把1的老大設定成5,5的幫派因此人數增加。1也因為不是最終老大,所以size[1]沒有用了。 * find 路徑壓縮 路徑壓縮就是在找老大的過程中 順便把路徑上所有點的老大也設成最終老大 ``` cpp int find(int index) { if(parent[index] == index) return index; return parent[index] = find(parent[index]); // 路徑壓縮 } ``` * union 合併兩個集合a, b ``` cpp void Union(int a, int b) { int fa = find(a), fb = find(b); size[fa] += size[fb]; parent[fb] = fa; } ``` * 啟發式合併 把小的集合接在大的集合下面,之後find會更快 ``` cpp void Union(int a, int b) { int fa = find(a), fb = find(b); if(size[fb] > size[fa]) swap(fa, fb); size[fa] += size[fb]; parent[fb] = fa; } ``` * 時間複雜度 都沒有: O(n) 路徑壓縮: O(log n) 啟發式合併: O(log n) 啟發式合併+路徑壓縮: O(α(n)) (α = 反阿卡曼函數) * 課後練習 [Kattis - Ladice](https://open.kattis.com/problems/ladice) [Kattis - Association for Control Over Minds](https://open.kattis.com/problems/control) [Codeforces - Social Network](https://codeforces.com/contest/1609/problem/D) --- ## [Part 3. Heap](https://www.youtube.com/watch?v=cdakZQa2pXE&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=3) ### 1. 資料型態 * min heap (最小堆積):母節點的值恆小於等於子節點 * max heap (最大堆積):母節點的值恆大於等於子節點 ### 2. 操作 (以 min heap 為例) * push() 將元素加入 heap 中 * top() 從 heap 中取出最小元素 * pop() 將 heap 中最小的元素去除 還有很多,只是這些夠玩了 ### 3. 如何實現 把資料擺成 Complete Binary Tree 保證子樹一定不小於自己,但左右子樹大小不保證 示意圖: ![](https://i.imgur.com/MIZN7Da.jpg) ### 4. 樹的儲存方法 因為是 binary tree,所以可用array存取。 根節點 idx=0 對於所有節點,若節點編號為 i,則左邊的孩子編號為 2i+1,且右邊的孩子編號為 2i+2 ### 5. how 函式 work? * **top** 獲得最小值(根節點) * **push** 把要加入的值放入heap最後面,再慢慢跟上面的值比大小 例如:把0加入heap中 ![](https://i.imgur.com/MIZN7Da.jpg) ▼ 先把0放到最後面(vector的push_back()) ![](https://i.imgur.com/JMWYWUT.jpg) ▼ 跟上面的值比大小,比較小就往上移,像泡泡浮起來一樣 ![](https://i.imgur.com/fUUKC0B.jpg) ![](https://i.imgur.com/MKI6hVu.jpg) ▼ 比到最後,就會發先0是最小的,所以位置在根節點 ![](https://i.imgur.com/NhjQRLk.jpg) * **pop** 如果直接把最小值(根節點)刪掉,heap就會分裂成2個,下次要找最校值就會比較難處理。所以先把最小值和尾端交換,再把最後一個元素刪除(pop_back),然後將現在的根節點和下面兩個值比,並且跟最小值交換 例如:把0刪掉 ![](https://i.imgur.com/wqST8lV.jpg) ▼ 先把0和最後一個元素交換 ![](https://i.imgur.com/fewUViD.jpg) ▼ 刪除最後一個元素 ![](https://i.imgur.com/OJKoYTg.jpg) ▼ 讓19和下面兩個值比大小,並跟最小值(1)交換 ![](https://i.imgur.com/F8czb8H.jpg) ![](https://i.imgur.com/zO4nO0J.jpg) ▼ 繼續比直到19為3數值中最大或到達根部 ![](https://i.imgur.com/pH5woi1.jpg) ### 6. 時間複雜度分析 * top: O(1) * push, pop: O(log n) ### 7. 課後練習 priority queue [Kattis - Jane Eyre](https://open.kattis.com/problems/janeeyre) [Kattis - Continuous Median](https://open.kattis.com/problems/continuousmedian) ## [Part 4. BIT (Binary Index Tree)](https://www.youtube.com/watch?v=bCsU5hlc5jI&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=4) 用來處理動態區間和 BIT 會把原本的陣列 用很多長度為二的冪次的小塊區間表示, 在計算或是修改時,都去對那些小區間修改。 使用 BIT 會將時間複雜度變為 O(log n) * bit[maxn] : 長度為整個區間 [1, n] 的一維陣列。 上面每個點儲存的不是該點真正的值,而是從這個節點往前**二的冪次**長度的區間和。 注意這是 1-base 的陣列。 ![](https://i.imgur.com/lLewWkt.png) * lowbit:我們定義最右邊出現的第一個 1 為 lowbit。 e.g. 10010 的 lowbit 為右邊第二個 bit 的 1。 一個點編號 13=8+4+1=1101 (2-base) 可以由 [1,8]+[9,12]+[13,13] 加總起來疊加出正確的 [1,13] 區間。 至於為什麼是這些區間,跟 lowbit 有很大關係。 #### 要如何正確且快速地找出那些小區間塊呢? 不斷找出當前 index 的 lowbit 將其扣掉,得出新的 index。 在查詢[1, i]的區間和時,先查格子 i (1101), 再把lowbit拿掉,如此重複下去: ``` cpp i=1101 -> sum+=bit[1101], i-=0001 i=1100 -> sum+=bit[1100], i-=0100 i=1000 -> sum+=bit[1000], i-=1000 i=0000 -> break ``` #### 只花 O(1) 找出 lowbit 的方法: ``` cpp lowbit = i & -i; ``` 舉例: i = 10100, -i = 01100 (二補數) i & -i = 00100 原理: i 與 -i 加起來要讓它變成全部位數都是0 (溢位不算) 然後取 i 與 -i 位數都是 1 的就是 lowbit > **二補數** 一個數的二補數是從它二進位表示的最左方開始,找到第一個 1 之後,右邊的 0, 1 互換 * query (詢問) 在詢問某區間和時 只要用剛剛的方法看他被哪些小區塊覆蓋 把他們加起來就是該區間和。 ``` cpp ll sum(int i) { ll ret = 0; while(i > 0) ret += bit[i], i -= i & -i; // 1-base return ret; } ``` 要查詢閉區間 [l, r] 時,只要找出 [1, r],扣掉 [1, l - 1],就是[l, r] 的區間和了。 #### 但要修改怎麼辦? 當我們改了 index 這個點,要怎麼知道上面有哪些點有包含 bit[index] 所代表的區間呢? * 單點修改 假設要修改 5 = 101 (2-base) 先改 i 的值,再把 i 加上 lowbit,反覆改 i,加值。 ``` cpp i = 101 -> bit[101]+=val, i = 110 i = 110 -> bit[110]+=val, i = 1000 i = 1000 -> bit[1000]+=val, i = 10000 i = 10000 -> bit[10000]+=val, i = 100000 // ...直到 i 超過 maxn -> break ``` * 區間修改 可以用線段樹做(詳情見Part 5. Segment Tree) * 先找出某個單點 i,被哪些子區間重疊到 要把這個單點的值增加 val 就把跟他有關的所有區間都加上 val ``` cpp void modify(int i, int val) { while(i <= MAXN) bit[i] += val, i += i & -i; } ``` #### 課後練習 [TIOJ - 1080](https://tioj.ck.tp.edu.tw/problems/1080) [Codeforces round 196 - C](https://codeforces.com/contest/276/problem/C) --- ## [Part 5. Segment Tree](https://www.youtube.com/watch?v=Hfue-_pVFPc&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=5) 前面 BIT 有提到,做區間和的時候如果每次都從頭計算,詢問的話要花 O(n) 的時間,實在是有點太久了。你其實可以動一下腦,你就會發現,你可以預處理好前綴和,前綴和做完可以大幅加速詢問的效率。但缺點是,你每修改一個值,你就要花 O(n) 時間改你做好前綴和的表。 於是線段樹這個東西就出現了,它是個很好玩的資料結構。 ### 1. 功能介紹 <font color = "#f00" size = 4>每次操作都是 O(log n)!!!</font> * 單點修改 * 區間查詢 * 區間修改 線段樹有另一個好處是,它不只可以求區間和,它可以做任何有結合律的二元運算子的操作,像是找最小值、最大值,或是乘法。 當你要訪問一個區間的時候,其實不用按照你擺的順序從頭到尾做。 ### 2. 基本概念 * 善用運算子具有結合率的性質 * 每次詢問時,將大問題切成子問題 * 事先預處理一些區間的答案 * 更新時只需要更新與之相關的區間 ### 3. 實現方法:建樹 * 所有節點都有各自代表的範圍的答案 * 每個節點都可以利用子樹資訊得到 * 葉節點存初始陣列數值 * 用陣列存最多只有 4n 個節點 ▼ 例如: arr = [-9, 11, 1, 3, -7, -4, 10, -5] ![](https://i.imgur.com/bxDzKmT.jpg) 它的存法跟 Heap 一樣,因為它也是一棵二元樹,所以你可以把根節點的 index 設成 0,它的 left child 和 right child 就分別是 2n+1 和 2n+2。 ### 區間查詢 在 [l, r) 區間內詢問 [L, R) 時只會有以下三種狀況: * 1. [L, R) = [l, r) * 2. [L, R) ⊊ [l, r) 2-1. 目標區間在左半或右半 ( L ≥ mid ∨ R ≤ mid ) 2-2. 目標區間橫跨中點 **狀況一**:目標區間與當前區間相等 直接回傳當前區間 **狀況二**:目標區間與當前區間不相等(都在左半或右半) 縮小問題,進入左(右)子區間 **狀況三**:目標區間與當前區間不相等(橫跨中點) 縮小問題,分別進入左、右子區間詢問 回傳子區間合併後的答案 例:求 [2, 4) + [4, 7) 的區間和 ![](https://i.imgur.com/7Zw3FrF.jpg) ![](https://i.imgur.com/ptT7NAy.jpg) ▼ 會變成求 [2, 4) + [4, 6) + [6, 7) 的區間和 ![](https://i.imgur.com/ef1mcS2.jpg) ![](https://i.imgur.com/o7eguGO.jpg) 最後答案是 4 + (-11) + 10 = 3 ### 單點修改 利用類似二分搜的想法找到目標 回溯時依序更改與之相關的節點資訊 例:a[2] = 7 ![](https://i.imgur.com/4k5hq9e.jpg) ![](https://i.imgur.com/PY993Nx.jpg) ![](https://i.imgur.com/5KpstX3.jpg) ![](https://i.imgur.com/24sdOIQ.jpg) ![](https://i.imgur.com/6OVP7sU.jpg) ![](https://i.imgur.com/X2qmIVV.jpg) ![](https://i.imgur.com/DzyMJiF.jpg) ![](https://i.imgur.com/Bf4ILRW.jpg) ### 4. 使用時機 * max(),min(),gcd(),lcm() * +, *, 矩陣乘法 * 線段樹上二分搜 二分搜 query 出來的答案需要 O(log^2^ n) 但直接在線段樹上二分搜只需要 O(log n) A. 0 的個數 (搜尋第 k 個 0 在哪裡) B. 給定前綴和 C. lower_bound ### 5. 進階技巧 * lazy tag (懶標) 懶標代表該區間應該被修改,但尚未操作 * 區間修改 #### 區間修改實作 經過含有懶標的節點時,順便推懶標 pull 之前確定左右子樹已被懶標作用 ``` cpp= typedef long long loli; ``` ``` cpp= void push(int l,int r,int v){ summ[v]+=tag[v]*(r-l); if(r-l==1) return tag[v]=0,void(); tag[2*v+1]+=tag[v]; tag[2*v+2]+=tag[v]; tag[v]=0; } ``` ``` cpp= void pull(int l,int r,int v){ if(r-l==1) return; int mid=(l+r)/2; push(l,mid,2*v+1); push(mid,r,2*v+2); summ[v]=summ[2*v+1]+summ[2*v+2]; } ``` **建樹** 遞迴把左右子樹建出來 拿建好的兩棵子樹合併出自己 ``` cpp= void build(int l,int r,int v=0){ if(r-l==1){ summ[v]=arr[l]; return; } int mid=(l+r)/2; build(l,mid,2*v+1); build(mid,r,2*v+2); pull(l,r,v); } ``` #### 區間查詢實作 在 [l,r) 中查詢 [L,R) 區間的答案 ``` cpp= loli query(int L,int R,int l,int r,int v=0){ push(l,r,v); if(l==L && R==r) // 區間相同 return summ[v]; int mid=(l+r)/2; if(R<=mid) // 在左半邊 return query(L,R,l,mid,2*v+1); else if(mid<=L) // 在右半邊 return query(L,R,mid,r,2*v+2); else // 區間橫跨中點 return query(L,mid,l,mid,2*v+1) +query(mid,R,mid,r,2*v+2); } ``` #### 區間修改實作 在 [l,r) 中將 [L,R) 區間的值增加 val 找到 [L,R) 後,將其懶標打上 val ``` cpp void update(int L,int R,loli val,int l,int r,int v=0){ push(l,r,v); if(l==L && R==r){ tag[v]+=val; push(l,r,v); return; } int mid=(l+r)/2; if(R<=mid) update(L,R,val,l,mid,2*v+1); else if(mid<=L) update(L,R,val,mid,r,2*v+2); else update(L,mid,val,l,mid,2*v+1),update(mid,R,val,mid,r,2*v+2); pull(l,r,v); } ``` ### 6. 課後練習 [Codeforces - DZY Loves Fibonacci Numbers](https://codeforces.com/contest/446/problem/C) [Codeforces - Circular RMQ](https://codeforces.com/problemset/problem/52/C) [Kattis - Just for Sidekicks](https://open.kattis.com/problems/justforsidekicks) [Codeforces - Array Stabilization (GCD version)](https://codeforces.com/problemset/problem/1547/F) [Codeforces - Not Equal on a Segment](https://codeforces.com/contest/622/problem/C) [Codeforces - Boring Segments](https://codeforces.com/contest/1555/problem/E) [Ab Initio](https://open.kattis.com/problems/abinitio) [Ultra-QuickSort](https://open.kattis.com/problems/ultraquicksort) [Codeforces - NGGYU](https://www.youtube.com/watch?v=dQw4w9WgXcQ) --- <font size = 5>結束了!!!好耶!!!