# 12/21~1/9 備戰APCS與心得 終於要考第二次APCS了,但老實說跟十一月那次間隔有點短,再加上大部分時間都在寫網頁,這次能學得新觀念不是太多,但既然我在高一下之後就沒在紀錄競賽程式,這邊就做個暑假到高二上學的統整跟考後心得 ## Linked list Linked list,又稱鏈結串列,是個有點特別的資料結構,每一節點,都包含自身的資料跟指向下/前一個的指標,我喜歡把它想成地點與告示牌,像是你走進公園裡,第一個地點是長凳,上面有個牌子寫著溜滑梯,你就知道下一個景點是溜滑梯,走過去又發現一個寫著翹翹板的牌子,你就能繼續走下去 話說我每篇學習歷程都有前一篇跟下一篇的連結,應該也算一種linked list(〜 ̄▽ ̄)〜 ### 基本架構 最基本的一個節點長這樣 ```cpp= struct node { int val; struct node *prev, *next; //告示牌 往前或往後 }; ``` 但老實說我在實作的時候更常用陣列做linked list,不是刻節點,只是這樣就不能做記憶體的釋放 ```cpp= struct node { int val; struct node *next; }; node first; void traversal() { node *current = first; while (current != NULL) { // do something current = current-> next; } } void deleteNode(int x) { node *current = first, *prevNode = NULL; while (current != NULL && current -> val != x) { previous = current; current = current -> next; } if (current == NULL) { return; } else if (current == first) { first = current -> next; delete current; } else { prevNode -> next = current -> next; delete current; } } void reverse() { node *current = first, *prevNode = NULL, *nextNode = currentNode -> next; while(nextNode != NULL) { currentNode -> next = prevNode; prevNode = currentNode; currentNode = nextNode; nextNode = nextNode -> next; } current -> next = prevNode; first = current; } ``` ### 遍歷與查找 就是順著pointer走 ```cpp= ``` 陣列版我習慣用for寫 ```cpp= node arr[105]; ``` 然而這也是linked list的弱點,因為只能不斷循著pointer走,所以查找複雜度不佳,是O(n) ### 編輯節點 而Linked list的優點是刪除或插入中間項十分快速,以刪除舉例,只要把自己前一項的pointer指向自己的下一項,就能輕鬆越過自己了,時間複雜度O(1) ```cpp= //假設欲刪除一叫做currentNode的節點 node *currentNode, *prevNode = currentNode -> prev, *nextNode = currentNode -> next; prevNode -> next = currentNode -> next; nextNode -> prev = currentNode -> prev; ``` 陣列版操作比較簡單 ```cpp= //假設刪除arr[i] node arr[105]; arr[arr[i].prev].next = arr[i].next; arr[arr[i].next].prev = arr[i].prev; ``` 這是linked list最厲害的一點,但在打競賽時,其實不到非常常用,因為有set,刪除跟插入O(logn)還不錯,查找O(logn)也比linked list快,使得linked list在競賽中變得不太主流,少數題型才會用到 ### 倒轉linked list 這在競賽裡面似乎不怎麼實用,但好像是企業面試的經典題,所以還是學了 重點就是要把當下的節點、前一個、下一個都存起來,把當下的節點的pointer轉向(前轉後,後轉前),再把三個節點通通往後移,重複做直到linked list尾端,並更新linked list的頭 ```cpp= node *currentNode = first, *prevNode = NULL, *nextNode = currentNode -> next; while(nextNode != NULL) { currentNode -> next = prevNode; currentNode -> prev = nextNode; prevNode = currentNode; currentNode = nextNode; nextNode = nextNode -> next; } first = current; ``` Linked list大概就先這樣,底下放幾題解題心得 ## 合併排序 ## Tree 樹則是更特殊資料結構,算是圖中的一種特殊狀況(突然想起來一年級的時候學習歷程只有打DFS跟BFS,忘了講圖論@@) ## 單調隊列 ## 奇怪的資結 ### 差分序列 ### BIT ```cpp= int n, BIT[MAXN]; int lowbit(int x) { return x & -x; } int modify(int x) { for(int i = x; i <= n; i += lowbit(i)) { // modify BIT[i] } } int query(int x) { int ret = 0; for(int i = x; i > 0; i -= lowbit(i)) { ret += BIT[i]; } return ret; } ``` ### 線段樹 ```cpp= int ST[MAXN]; void pull(int x) { //相加or比大小來合併左節點與右節點 } int query(int x, int l, int r, int ql, int qr) { //[ql, qr]: 查詢區間 if (ql <= l && qr >= r) { return ST[x]; } int ret = 0; int mid = (l + r) >> 1; // 此為區間和作法,若是RMQ就跟query比大小 if (ql <= mid) { ret += query(x << 1, l, mid, ql, qr); } if (qr > mid) { ret += query(x << 1 | 1, mid + 1, r, ql, qr); } return ret; } void update(int x, int l, int r, int pos){ if (l == r) { //更新ST[x] return; } int mid = (l + r) >> 1; if (pos <= mid) update(x << 1, l, mid, pos); else update(x << 1 | 1, mid + 1, r, pos); pull(x); } ``` ## 並查集 ```cpp= int root[MAXN], unionRank[MAXN]; // 上司跟集合大小 int findParent(ll x) { // 若上司是自己,代表你就是該集合的老大,若不是,遞迴的同時把上司改成老大 return root[x] == x ? x : root[x] = findParent(root[x]); } void unionByRank(int x, int y) { int rootX = findParent(x), rootY = findParent(y); // 老大相同不合併 if (rootX == rootY) { return; } // 用集合大小來決定誰併到誰理 if (unionRank[rootX] > unionRank[rootY]) { root[rootY] = rootX; unionRank[rootX] += unionRank[rootY]; } else { root[rootX] = rootY; unionRank[rootY] += unionRank[rootX]; } } ``` ## 拓樸排序 ```cpp= int n, in[MAXN] = {}; // 點數, 入度 queue<int> root, ans; // 當下入度=0的點, 拓排解答 vector<vector<int>> graph; void topoSorting() { // 初始化入度為0的點 for (int i = 1; i <= n; i++) { if (in[i] == 0) { root.push(i); } } while (!root.empty()) { int head = root.front(); root.pop(); ans.push(head); for (auto i: graph[head]) { in[i]--; if (in[i] == 0) { root.push(i); } } } } ``` ## Connected Component ```cpp= int low[MAXN] = {}, DFN[MAXN] = {}, currentDFN = 0, SCC[MAXN] = {}; bool inStack[MAXN] = {false}; // 還沒形成SCC的點 vector<vector<int>> edges; stack<int> st; void Tarjan(int v) { low[v] = DFN[v] = ++currentDFN; st.push(v); inStack[v] = true; for (int &i : edges[v]) { if (!DFN[i]) { Tarjan(i); low[v] = min(low[v], low[i]); } else if (inStack[i]) { // 還在stack中則SCC必尚未形成,排除掉連到已完成SCC的交錯邊 // 之所以是跟DFN[i]比大小不是跟low[i]是因為 // 這條邊可能是回邊或交錯邊(接回的子孫有回邊接到v祖先) // 不管怎樣,都至少已經有一回邊,而low的定義是只走一回邊 // 故只能用DFN[i]更新low[v] low[v] = min(low[v], DFN[i]); } } if (low[v] == DFN[v]) { int top; do { top = st.top(); st.pop(); SCC[top] = v; inStack[top] = false; } while (top != v); // v之後的都在此SCC中 } } ``` ## Fast Power ```cpp= int fastPow(int a, int n) { int ret = 1; while (n > 0) { if (n & 1) ret = ret * a; a = a * a; n >>= 1; } return ret; } ``` ## APCS心得 在講一月的APCS之前,先前情提要一下十一月那場 ## 最後的最後 --- <ul style ="display: flex; flex-direction: row; width: 100%; justify-content: space-between;margin: 0; padding: 0;"> <li style="list-style: none;"><a href = "https://hackmd.io/@WeberChang/Bysh-oH5K" >上一篇</a></li> <li style="list-style: none;"><a href = "https://hackmd.io/@WeberChang/rJBpwRsut">主頁</a></li> <li style="list-style: none;"><a href = "https://hackmd.io/@WeberChang/B1P1d5rpY">下一篇</a> </li> </ul>