隨便寫寫,把發現、作品、學習內容分享出來。正在攻讀資訊系,還在學習怎麼翹課。
最短路
Bellman-Ford Bellman-Ford 可以在有負邊的圖 (不論有向無向)上找出單源最短路,還可以偵測負環。在最短路存在的情況下,由於每次鬆弛最少會使最短路的邊數$+1$,而最短路的邊數最多為 $n - 1$ (每個節點最多經過一次),因此最多進行 $n - 1$ 次鬆弛操作。若到了第 $n$ 次迴圈還可以繼續鬆弛,則代表該圖存在負環。 ...
線性篩
原理 令當前要處理的數為 $i$,已找到的質數列表為 primes: 遍歷 $i$ 從 $2$ 到 $N$。 若 $i$ 未被標記為合數,則 $i$ 為質數,加入 primes。 用 $i$ 與 primes 中的質數 $p$ 相乘(標記合數 $x = i \times p$): 標記 $i \times p$ 為合數。 關鍵關鍵條件:若 $i \pmod p == 0$,立即中斷(break)內部迴圈。 為什麼 i % p == 0 時要 break? 線性篩規定「每個合數只能由它的最小質因數篩掉」,若 $p$ 能整除 $i$,表示 $p$ 是 $i$ 的最小質因數。 如果繼續使用下一個更大的質數 $p_{next}$ 來標記 $i \times p_{next}$,該數的最小質因數其實是 $p$ 而非 $p_{next}$。這樣會導致 $i \times p_{next}$ 未來又被別的更大的 $i$ 和 $p$ 重複篩到,破壞線性時間。 ...
線段樹
線段樹是一種資料結構,也可以說是一種分冶想法,用來在 $O(\log N)$ 內處理區間查詢、區間修改的問題。 甚麼是區間問題 比較簡單的區間問題如下: 有一個序列 $a_1, a_2, a_3, \cdots, a_n$ ,且有 $q$ 筆操作,操作有兩種: ...
利用 Obsidian 專注於寫作
之前用 hugo 架站的其中諸多痛苦是,我需要用 Vscode 打開 repo 寫文章、開終端機跑 hugo、開 localhost 查看文章效果、放圖片要手動複製、要想 commit message (這大概是最痛苦的),要更新得開多個視窗來達成一件事,所以讓我很不想在上面寫文章。 ...
最大流問題
甚麼是最大流問題 想像有一張由水管連接而成的網路,每條水管有容量限制,流過的水流不能超過這條水管的容量。這張圖上有兩個特別的節點:源點和匯點,源點是無限供水的地方,而匯點是收集水的地方。 source: https://www.geeksforgeeks.org/dsa/ford-fulkerson-algorithm-for-maximum-flow-problem/ 目標:調整各條管線的流量,讓匯點 $T$ 能有最大輸入。 名詞介紹 網路 Network:一個有向圖 源點與匯點 Source and Sink:源點 $S$ 為網路流的起點、匯點 $T$ 為網路流的終點,其餘點為中間點 流量 Flow:每條邊上的數值,表示經過該條邊的流量,計為 $f(u,v)$ 容量 Capacity :每條邊上的最大流量,計為 $c(u,v)$ 殘餘容量 Residual Capacity:每條邊上容量減去流量的值,計為 $r(u,v) = c(u,v) - f(u,v)$ 剩餘網路 Residual Network:計算每條邊上的殘餘容量,以 $r(u,v)$ 畫成新的一張圖 網路流量:由源點出發至匯點的總流量,若該值達到最大,則稱為「最大流」 增廣路徑:在殘餘網路中,存在一條從源點 $S$ 到匯點 $T$ 的路徑,其路徑上的每一條邊殘餘容量皆大於 0($r(u,v) > 0$),只要能找到增廣路徑,就代表當前的總流量還可以再增加 反向邊:殘餘網路還包含了反向邊,流量為 $f(u, v)$。反向邊的作用是給予演算法反悔的機制,當發現從不同路徑能夠達到的流量更大,且兩個路徑經過同一條邊但相反方向時,反向邊可以抵消原本的流量,讓兩條路徑分開為不同路徑,此時這條邊的實際流量就會是 0。 舉個反向邊的例子,考慮以下流量網路: 如果演算法先找到了 $S \rightarrow A \rightarrow B \rightarrow T$ 這條路徑,輸送了 10 單位的流量,此時 $A \rightarrow B$ 這條邊已經被占滿了。其實最佳解法應該是 $S \rightarrow B$ 與 $A \rightarrow T$ 這兩條路可以組出 $S \rightarrow B \rightarrow T$ 與 $S \rightarrow A \rightarrow T$ 這兩條路徑,可以組出 20 單位的流量,如果第一步時在 $A$ 、 $B$ 之間建立建立一條反向邊 $B \rightarrow A$ ,演算法就可以繼續走 $S \to B \to A \to T$。此時從 $A \to B$ 的流被抵銷了,兩條路徑被拆成都不經過 $A \to B$ 這條邊。 ...
第二類斯特林數
第二類斯特林數(Stirling Number of the 2nd Kind),是用來計算一種問題: 將 $n$ 顆不同的小球放入 $k$ 個相同的箱子中,不允許空箱,有幾種方法數? 通常記做 $S(n, k)$ 或 $\begin{Bmatrix}n\\ k\end{Bmatrix}$。 如何計算 可以思考一下,現在有 $n$ 顆球, $k$ 個箱子: $n - 1$ 顆球已經被放到了 $k - 1$個箱子中,現在要放入最後一顆球到最後一個空的箱子。 方法數會是 $S(n - 1, k - 1)$。 $n - 1$ 顆球已經被放到了 $k$ 個箱子中,現在要放入最後一顆球到其中一個箱子。 方法數會是 $C^n_1 \times S(n - 1, k)$ 。 因此可以寫出 $S(n, k)$ 的遞迴式: $$ S(n, k) = S(n - 1, k - 1) + C^n_1 \times S(n - 1, k) $$ 而第二類斯特林數有以下性質: ...
O(n)預處理組合數
由於 $C^n_r$ 是階乘,直接硬算會算很久,因此預先處理來快速算出組合數尤為重要。 想法是利用模反元素,將除以 $k!$ 改成乘以 $k!$ 的模反元素。這個方法預處理的時間複雜度為 $O(N)$,之後就能用 $O(1)$ 查詢。 步驟: 先開一個陣列紀錄 $0! \sim n! \pmod M$ 的值 計算 $n! \pmod M$ 的模逆元,再開一個陣列紀錄 $k! \sim 0!$ 的模逆元。 只要先求出 $(k!)^{-1}$ ,就可以利用 $(k!)^{-1} = (k + 1)!^{-1} \times (k + 1) \pmod M$ 來由大到小得出所有階乘的模逆元。 查詢時,只要利用 $C^n_k = n! \times (k!)^{-1} \times ((n - k)!)^{-1} \pmod M$ 在 $O(1)$ 得出。 #define MOD 1000000007LL #define MAXN 200005 #define ll long long using namespace std; ll qpow(ll a, ll b) { ll res = 1; a %= MOD; while (b > 0) { if (b & 1) { res = res * a % MOD; } a = a * a % MOD; b >>= 1; } return res; } ll inverse_mod(int a) { return qpow(a, MOD - 2); } ll fact[MAXN], inv_fact[MAXN]; void precompute_combination() { fact[0] = 1; inv_fact[0] = 1; for (int i = 1; i < MAXN; i++) { fact[i] = fact[i - 1] * i % MOD; } inv_fact[MAXN - 1] = inverse_mod(fact[MAXN - 1]); for (int i = MAXN - 2; i >= 1; i--) { inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD; } } ll nCr(int n, int r) { if (r < 0 || r > n) return 0; return fact[n] * inv_fact[r] % MOD * inv_fact[n - r] % MOD; }
Suffix Array
後綴是甚麼? 比方說字串 S = banana,S的所有後綴就是: banana anana nana ana na a 將這些後綴排序後: a ana anana banana na nana Suffix Array就是將字串的每個後綴進行排序後,紀錄該後綴開頭index的陣列: int SA[N]; SA[i]; // 表示順序第i個後綴的開頭index 如果 SA[3] = 0 ,表示字典序第 4 小的後綴是從 S[0] 開頭的,也就是 banana。 例題:算出 s = "aba" 的 SA 陣列 ans SA[3] = {2, 0, 1}; 怎麼找出 Suffix Array ? 暴力法就是把所有後綴抓出來,然後進行排序。顯然這個方法的時間複雜度是 $O(N^2\log N)$ (別忘了比較兩個字串要 $O(N)$)。 ...
台大資工大一下修課心得
羽球初級 課程資訊 ...
使用本地大語言模型翻譯Unity遊戲
打破語言隔閡!!(๑•̀ㅂ•́)و✧