# 演算法筆記 ## **目錄** ### 1.[STL](https://hackmd.io/L3i-dTqRTq-o0vZzCxd1XQ?both#STL) ### 2.圖論 ### 3.Sparse table ### 4.字串相關 ### 5.指標 ## 題單參考 [yui huang 的演算法筆記](https://yuihuang.com/oj-level-4/) [資讀題單](https://docs.google.com/spreadsheets/d/1pETbGRvHBhybv--MRj4N_khKetZDGWcsiRs70WNi_0A/edit#gid=589172402) ## **STL** ### (1)STACK * 從最頂端存取、刪除、新增資料 * 後進先出 #### 程式碼 ```cpp= stack <int> stk; stk.top(); //回傳stack頂端的值 stk.pop(); //刪除stack頂端的資料 stk.push(); // 將新值加入stack stk.size(); // 輸出stack的大小 ``` ### 補充:前序/中序/後序運算法 **前序** +35 **中序** 3+5 **後序** 35+ 那要如何轉換呢? **例題:後序運算法** 我們平常使用的是中序運算法,但是對電腦而言,使用後序運算法是較為方便的 如果要用程式來進行中序轉後序,則必須使用堆疊,演算法很簡單,直接敘述的話就是使用迴圈,取出中序式的字元,遇運算元直接輸出;堆疊運算子與左括號; 堆疊中運算子優先順序大於讀入的運算子優先順序的話,直接輸出堆疊中的運算子,再將讀入的運算子置入堆疊;遇右括號輸出堆疊中的運算子至左括號。 ### (2)QUEUE * 先進先出 * 從最前端存取、刪除資料,從最後端新增資料 ``` cpp= queue <int> q; q.front(); //回傳 queue 最前端的值 q.pop(); //刪除 queue 最前端的資料 q.push(); //將一個新的值加入 queue 的最後端 q.size(); //回傳 queue 的大小 ``` ### (1) + (2) = deque! (雙向佇列) 對了,deque可以用insert ```cpp= #include <deque> deque <int> dq; dq.empty(); //測試 deque 是否為空。(回傳布林值) dq.size(); //查詢目前 deque 中有幾個元素。 dq.push_front();//在 deque 的前端加入一個元素。 dq.push_back(); //在 deque 的後端加入一個元素。 dq.pop_front(); //移除目前 deque 中最前端元素。 dq.pop_back(); //移除目前 deque 中最後端元素。 dq.front(); //取得目前 deque 中最前端元素。 dq.back(); //取得目前 deque 中最後端元素。 dq.insert(iterator, n, val); //插入位置/次數/值 ``` ### (3)Heap (priority_queue) * 存取、刪除 heap 最頂端的資料 (為預設的極值) * 維持極值 :::success 預設為由大至小排列,但可以再進行更改 ```cpp= priority_queue <int> pq; //由大至小 priority_queue <T, vector<T>, greater<T> > pq; //由小至大 priority_queue<T, vector<T>, cmp> pq; //自行定義 cmp 排序 ``` ::: ```cpp= pq.push(); //輸入 pq.top(); //存取最大權重值 pq.pop(); //刪除最大權重值 pq.size(); //回傳 pioraity_queue 的大小 ``` ### (4)Set * 用於插入/刪除/選取資料 * 會自動進行排序/不會選到重覆的 ```cpp= set <int> s; s.insert(); //新增 s.erase(); //刪除 s.find(); //查詢 (與iterator有關) s.count(); //查詢是否存在於集合中 s.lower_bound(val); //找出大於或等於val的最小值的位置 for (auto it = s.begin(); it != s.end(); it++) // set的遍歷 cout << *it << endl; ``` ### 補充:lower_bound and upper_bound (二分搜) 排序過的vector也可以用! ```cpp= auto it = lower_bound(v.begin(), v.end(), val); //用二分搜找出 "大於或等於" x的最小值的 "位置" auto it = upper_bound(v.begin(), v.end(), val); //用二分搜找出 "大於" x的最小值的 "位置" ``` 還不太熟練,來幾道習題吧! [APCS-圓環出口](https://zerojudge.tw/ShowProblem?problemid=f581) [APCS-牆上海報](https://zerojudge.tw/ShowProblem?problemid=h084) [APCS-飛黃騰達](https://zerojudge.tw/ShowProblem?problemid=f608) [APCS-雷射測試](https://zerojudge.tw/ShowProblem?problemid=i401) [紀念品分組](https://zerojudge.tw/ShowProblem?problemid=b159) ### (5)Map * 使用**關鍵字Key**去索引該**關鍵字的值value** * 為一對一的功能 * 可以修改value, 不能修改Key * 包含插入、修改、刪除、尋找 ```cpp= map <string, int> mp; //宣告一個 key為string, value為int 的map //插入的方法 mp[key] = value; //也可進行更改 mp.insert(pair<string, int>(Hi, 1) ); //查詢 mp.find(); //返回資料所在位置,若無則返回與end()函數相同 mp.count(); mp.erase(); //刪除 mp.clear(); //清空 ``` ### (6)Vector * 可改變容量的陣列 ```cpp= vector <int> adj; adj.push_back(); //新增尾部元素 adj.pop_back(); //刪除尾部元素 ``` ### (7)Pair * 包含兩個數值 前面的稱為first, 後面的稱為second * 兩個數值可以為不同種類的數據類型 ```c++= #include <utility> pair <T1, T2> p1 // 宣告 make_pair (v1, v2) //直接使用v1 / v2來創建這個pair //pair的操作 p1.first = 1; p1.second = 2; ``` ### (8)struct 常常在排序時用到! ```c++= struct A // 結構名稱 { 結構變數; int age; int weight; bool female; //之類的 }; //這裡要有分號,別忘記了! struct A a; a.age = 16; //以此改變 ``` ### [補充:指標(還是搞不太懂= =)](https://hackmd.io/L3i-dTqRTq-o0vZzCxd1XQ?view#%E6%8C%87%E6%A8%99) ### (9) Linked list 可支援插入以及刪除某個元素,但能夠存取的節點只有開頭 ```cpp= struct Node { int val; Node *_next ; Node *_before ; Node() { _next = NULL; _before = NULL; } }; ``` ### (10) bitset ```cpp= bitset<N> b(a); // 宣告一個大小為N的bitset, 後面的a是初始化 b.count() //回傳有幾個位元是1 b.set() //將所有位元設為1 b.reset() //將所右位元設為0 ``` ## 二分搜 1. 左閉右閉 ```cpp= int binary_search(int l, int r) { while(l < r) { int m = (l+r)/2; if ( f(m) == 1 ) r = m; else l = m+1; } return l; } ``` ## 二元樹 例子: [洛谷 P5076](https://www.luogu.com.cn/problem/P5076) 模板,其實跟線段樹有點像? 但傳上去只有86分,可能有bug! ```cpp= 初始建構 struct Node { int val, cnt=1, siz=1; struct Node *l=NULL; struct Node *r=NULL; }; void add(Node *now, int x) { now -> siz++; if (x < now -> val) { if (now -> l != NULL) add(now -> l, x); else { Node *son = new Node; son -> val = x; now -> l = son; } } else if (x > now -> val) { if (now -> r != NULL) add(now -> r, x); else { Node *son = new Node; son -> val = x; now -> r = son; } } else now -> cnt++; } //查詢x的排名 int query_x( Node *now, int x) { if (now == NULL) return 0; if (now -> val == x) { if (now -> l != NULL) return now -> l -> siz; return 0; } if (now -> val > x) return query_x( now -> l, x); else { if (now -> l != NULL) return now -> l -> siz + now -> cnt + query_x( now-> r, x); else return now -> cnt + query_x(now -> r, x); } } //查詢排名第x int query_num(Node *now, int rank) { if (now == NULL) return 2147483647; int lsiz; if (now -> l == NULL) lsiz = 0; else lsiz = now -> l -> siz; if (lsiz >= rank) return query_num( now->l, rank); if (lsiz + now -> cnt >= rank) return now -> val; else return query_num( now -> r, rank - lsiz - now->cnt); } //求x前面的數 (小於x且最大的數) int query_front( Node *now, int x, int ans) //找較小值 { if (now->val >= x) //比目前大就找左子樹 { if (now -> l != NULL) //有更小的就往左找 return query_front(now -> l, x, ans); else return ans; // 沒更小的了 } else { if (now -> r == NULL) //看有沒有比目前更大的,沒有輸出目前 return now -> val; else { //有就往右子樹找,先把目前可能答案紀錄 return query_front(now -> r, x, max(ans, now -> val)); } } } //求x後面的數 (大於x且最小的數) int query_next(Node *now, int x, int ans) { if (now -> val <= x) { if ( now -> r != NULL) return query_next(now -> r, x, ans); else return ans; } else { if (now -> l == NULL) return now -> val; else return query_next(now-> l, x, min(ans, now-> val)); } } ``` ## 圖論 ### 圖的儲存方式 可分為**鄰接矩陣**與**鄰接陣列** * **鄰接矩陣** 在鄰接矩陣中,建立一個二維陣列A 若(u, v)之間有連通,則使A[u][v] = 1, 否則A[u][v] = 0 在無向圖中記得A[v][u]也要設定 ```c++= bool A[M][M] = 0; int u, v; cin >> u >> v; A[u][v] = 1; A[v][u] = 1; ``` * **鄰接陣列** 對於每一個點儲存一個vector, 記錄他連到的所有點 ```cpp= vector <int> adj[M]; int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); ``` ### 圖的遍歷 分為**BFS(廣度優先搜尋法)** 以及 **DFS(深度優先搜尋法)** * **BFS** 使用**queue**存下可以走到但尚未走過的點 然後將走過的點pop, 繼續走與他相鄰的點 * <font color="red">**可以用來記錄每個點與原點的距離!** </font> 假設每一點的權重均為1 ```cpp= vector <int> adj[M]; bool vis [M] = {0}; //紀錄是否走過 int d[M] = {-1}; //紀錄距離 queue <int> que; d[1] = 0; que.push(1); while (que.size()) { int now = que.front(); que.pop(); vis[now] = 1; for (int v:adj[now]) //對於adj[now]中的每一個點都跑一遍 { if (!vis[v]) { que.push(v); // 還沒跑過就加入que d[v] = d[now] + 1; vis[v] = 1 //並紀錄已經跑過 } } } ``` * **DFS** 走目前能走的位置,一樣記錄每個點是否走過 ```cpp= vector <int>adj[M]; bool vis[M] = {0}; DFS(1); void DFS(int n) { vis[n] = 1; for (int v:adj[n]) { if (!vis[v]) DFS(v); } } ``` ### 拓撲排序 當事情有**先後順序**的時候,輸出做事情的順序 思路:能做的就先做完! 將事情的先後順序看成**有向圖**,當做過該事件後刪除此點 ```cpp= vector <int> adj[M]; int deg[M] = {0}; //紀錄每個點從哪裡來的! 0即為前面沒有要先做的 int main() { for (int i=0; i<m; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); deg[v]++; } queue <int> que; for (int i=1; i<=n; i++) if (deg[i]==0) //若事情前面沒有要先做的事就先跑 que.push(i); while (que.size()) { int now = que.front(); que.pop(); for (int v:adj[now]) { deg[v]--; if (deg[v] == 0) que.push(v); } } } ``` ### 樹 有n個點,n-1條邊的連通圖 除了**根節點**以外,每個點都有**父節點** * 在樹上進行**DFS**! 可以不用紀錄visited :::success 對於考到樹的題目,可以嘗試往整個子樹去想,不要只考慮單一節點之間的關係 ex: TOI2022 共享自行車 2020 建設人工島 ::: ```cpp= void dfs(int n, int par) { for (int v:adj[n]) if (v != par) dfs(v, n); } ``` * 記錄節點的**深度**! ```cpp= int d[M]; d[1] = 0; //此處先假設根節點為1, 要將其換成根節點喔! void dfs(int n, int par) { for (int v:adj[n]) if (v != par) { d[v] = d[n] +1; dfs(v, n); } } ``` 或者這樣也可以! ```cpp= int dep[M]; void dfs(int n, int par, int d) { dep[n] = d; for (int v:adj[n]) if (v != par) { dep[v] = dep[n] +1; dfs(v, n, d+1); } } ``` * **樹直徑** 先隨機找一點A, 找出距離其最遠的點B, 再找出距離點B最遠的點C, 點B和點C的距離即為答案! ```cpp= int dep[M]; vector <int> adj[M]; int dfs(int, int, int); int main() { for (int i=0; i<m; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } int u = dfs (1, 0, 0); int v = dfs(u, 0, 0); cout << dep[v]; } int dfs(int n, int par, int d) { dep[n] = d; int far = n; //紀錄距離最遠的點 for (int v:adj[n]) { if (v!= par) { int tmp = dfs(v, n, d+1); if (dep[tmp] > dep[far]) //若有更遠的就更新 far = tmp; } } return far; } ``` 相關題目:[CSES tree diameter](https://cses.fi/problemset/task/1131) #### 稍微進階的樹直徑 [CSES Tree distance](https://cses.fi/problemset/task/1132) 對每個點先找他的下面的距離 然後找那個點的parent, if(d[往下走的] + 1 > d[parent]) 答案就會是d[往下走的] #### **子樹大小** #### **樹重心** ### LCA(樹的共同祖先) 有兩點a, b, 其中a**深度**較深 * 將a點走至與b點深度相同 * 若此時a = b, a即是答案 * 當深度兩者相同時,搜尋他們的祖先,記錄從何時不同 * 何時不同的上一個祖先就是答案 ```cpp= vector <int> adj[M]; int anc[18][M]; //記錄他們的祖先 前面紀錄為第幾層祖先 int dfs (int n, int par, int d) { anc[0][n] = par; dep[n] = d; for (int v:adj[n]) if (v != par) dfs (v, n, d+1); } int lca(int a, int b) { if (dep[a] < dep[b]) swap(a, b); //從深度較深的開始計算 for (int i=17; i>0; i--) { if (dep[anc[i][a]] >= dep[b] ) a = anc[i][a]; //此時a的深度必定大於或等於b, 而當小於b時就不會再更新 } if (a == b) return a; for (int i=17; i>=0; i---) { if (anc[i][b] != anc[i][a]) //紀錄何時不同 a = anc[i][a]; } return anc[0][a]; //何時不同的祖先即為答案 } int main() { for (int i=1; i<18; i++) //紀錄祖先的祖先 { for (int j=1; j<node; j++) { anc[i][j] = anc[i-1][anc[i-1][j]]; } } cout << lca(a, b); //開始lca! } ``` 相關題目:[CSES company queries 2](https://cses.fi/problemset/task/1688) ### 並查集 (DSU) 用以處理**不交集**的**合併**和**查詢**問題 可以查詢兩點是否相通 透過不斷尋找**源頭**進行 ```cpp= p[M] = {-1}; //先將陣列進行初始化 int find_root (int x) //找尋源頭 { if (par[x] != x) //若x的源頭不等於x代表還有源頭,繼續找 return find_root(par[x]); return x; } ``` 當想讓兩者合併時 先將兩者的源頭都找出來,將其中一個源頭的源頭改為另一個源頭 ```cpp= void union(int x, int y) { int rx = find_root(x); int ry = find_root(y); par[rx] = ry; } ``` 相關題目:[畢業旅行](https://zerojudge.tw/ShowProblem?problemid=d831) [我的朋友很少](https://zerojudge.tw/ShowProblem?problemid=a445) ### 最短路 #### (1) Dijkstra演算法 適用於**沒有負邊**的情況下 想成帶有權重的BFS, 將權重和接收點用pair表示! 1. 維護一個priority_queue , 裡面存放該點的最小距離以及該點 (dis, now) 2. 取出**距離最小**的該點,若該點未被處理過,則dis即為此點的最小距離 (直接到步驟3) 3. 對now的相鄰點進行鬆弛(此時你要確定now已經不能再被鬆弛) ```cpp= priority_queue<pii, vector<pii>, greater<pii>> pq; dis[1] = 0; pq.push({0, 1}); while(pq.size()) { ll d = pq.top().f; int now = pq.top().s; pq.pop(); if (d != dis[now]) continue; // 如果不等於代表這一點已經被鬆弛過了 for (pii p: adj[now]) { int v = p.f, w = p.s; if (dis[v] > d+w) { dis[v] = d+w; pq.push({dis[v], v}); } } } ``` #### (2) Bellman-Ford演算法 與先前的Dijkstra略有不同,可以運用在**有負邊**的情況,也可以檢查是否有<font color="#f00">負環</font>! 複雜度為 O($nm$) 這一點要注意!已經因為這樣好幾次TLE了= = ```cpp= const ll inf = 1LL << 60; queue<ll> q; q.push(1); while(q.size()) { int now = q.top(); q.pop(); for (pll p: adj[now]) { ll v= p.f, w = p.s; if (dis[v] > dis[i]+w) { dis[v] = dis[i]+w; q.push(v); } } } ``` ### 二分圖 1. 用DSU做 當有n個點時開2*n個區域,若點a與點a+n在一起就表示不是二分圖 ### MST (最小生成樹) 1. Krusal algorithm 將邊依照權重從小到大排序,檢驗目前的點是否已經在此集合裡(用並查集檢查) 複雜度為 **O($mlogm$)** ```cpp= struct side { ll x, y, w; } sd[M]; bool cmp(sd a, sd b) { return a.w < b.w; } for (int i=0; i<m; i++) cin >> sd[i].x >> sd[i].y >> sd[i].w; sort(sd, sd+m, cmp); ll sum = 0; for (int i=0; i<m; i++) { if (find(sd[i].x) == sd[i].y) continue; Union(sd[i].x, sd[i].y); sum+=sd[i].w; } cout << sum << endl; ``` 2. Prim's algorithm 隨便選一個點當開頭,找**目前與已連接的點所連接的最小邊**,做法類似dijkstra (下面程式碼尚未完成) 複雜度為 **O($n^2$)** 或 **O($m logm$)** ```cpp= vector<pii> adj[M]; for (int i=0; i<m; i++) { int a, b, w; cin >> a >> b >> w; adj[a].push_back(w, b); adj[b].push_back(w, a); } for (int i=0; i<n; i++) sort(adj[i].begin(), adj[i].end); int sum = 0; priority_queue<pii>pq; ``` ## 區間問題 整理在這個[簡報](https://slides.com/yujuan/desk)裡了! 題目敘述:給你一個長度為N的數列,接下來有q筆詢問 詢問的類型有: (1)將[l, r]之間的數字全部加上x (2)輸出[l, r]這個區間的最大值 ### 前綴和 當問 [l, r]之間的和時 先記錄從第 1 項到第 i 項的和 那第 l 項到 第 r 項的和就是 (1-r) - (1-l) ### 差分序列 當我們有 [ a1, a2, a3, a4.. an]的序列時 紀錄 ( a2 - a1 ) , ( a3 - a2 ) , ### Sparse table 有概念了,但不太清楚,先放個別人的[參考程式碼](https://hackmd.io/@alanlai/HkZ5NqyDt) #### 概念: 將其先一開始每個均為一塊,然後將其不停**合併** (不同塊之間可重疊!) ![](https://i.imgur.com/mVvIH03.jpg) 以$s[i][j]$表示從 $i$ 開始, 長度為$2^j$的範圍 (也就是終點為 $i + 2^j -1$!) 例如$s[i][0]$表示從$i$開始,長度為$2^0 = 1$的範圍,也就是只有$i$自己! $s[i][1] 會等於 s[i][0] + s[$ 所以當我們要求長度為$n$的最大值時,需要 **$j = [\log_2n$]** 然後用DP更新 ```cpp= int N; int s[1005][1005]; int log2[1005]; //紀錄那個數字log2是多少 int num[1005]; for (int i=0; i<N; i++) { cin >> num[i]; s[i][0] = num[i]; } //2的j次方為 1 << j 或 pow(2,j) //建n以內log2的表 log2[1] = 0; for (int i=2; i<N; i++) log2[i] = log[i/2] +1; //做無條件進位 for (int j=1; j<log2[N]; j++) { for (int i=0; i+pow(2,j) - 1<N; i++) s[i][j] = max(s[i][j-1], s[i+pow(2,j)][j-1] ) ; } ``` ## 字串 ### 處理字串的方式 ### Stringstream的用法 可以用來將字串進行分割,或將其與Int進行轉換,首先引入標頭 ```cpp= #include <sstream> ``` **(1)將int轉換成stream** stringsteam提供 **<<** 和 **>>** 來進行**讀取**以及**輸入** ```cpp= #include <sstream> int main() { stringstream s1; int number 1234; string output; // 要進行轉換的string容器 s1 << number; //將number的值輸入至s1中 s1 >> output; cout << output; //如此一來得到的就會是1234啦! } ``` 相反的,也可以將stream轉換成int! **(2)字串分割** 先進先出,會以空白鍵作為分界 ```cpp= stringstream ss; string a = " I am a girl"; ss << a; string output; ss >> output ; cout << output ; // 這樣就會得到I! ss >> output; cout << output; //此時就會得到am囉 ``` ### 小數點後幾位 https://it-easy.tw/cout-float/ 運用 setprecision ```cpp= cout << fixed << setprecision (2) << 1.235 << endl; //這樣輸出結果就會是1.23! ``` ### 對齊我的輸出~ 運用setw(); ```cpp= cout << setw(4) << 123456; //這樣輸出結果就是1234! ``` ### Substr 顧名思義就是子字串 用法為 string (開頭位置,後面幾個), 若無結束位置就到底 ```cpp= string a = "abcdefg"; cout << a.substr(2, 5); // cdefg ``` ### replace 取代字串 ```cpp= string.replace(n1, k, string2); //將s1中從n1開始後k個用s2取代 ``` ### to_string(待查) ### next_permutation ### getline ```cpp= getline(cin, str, '結束字元'): example: str = 123456; cout << getline(cin, str, '5'); //answer = 12345 ``` ## 台大資管會用到的函式(字串 ### cin.getline(跟樓上的很像) only for char string! (char 資料型態的string) ```cpp= cin.getline(str, len); ``` ### 大小寫 ```cpp= toupper(char) //變大寫 tolower(char) //變小寫 isupper(char) //判斷是否是大寫 islower(char) //判斷是否是小寫 ``` ### strchr ```cpp= ``` ### strstr ### ispunct ## DP 排列 ```cpp= ``` 組合 n 為可能幣值 , x為總和 ```cpp= for (int i=1; i<=n; i++) for (int j=0; j<=x; j++) if (i-coin[j] >= 0) { combination[j] = combination[j-coin[i]] + combination[j]; } ``` 範例: 現有 (1, 3, 5)共3種幣值,要湊5元,可能的組合為? 當只有1元組合時,有 1 種 當有1、3元組合時,可以在 1有兩個的時候出來一個分支,所以有1+1 = 2種 組合與排列回圈內外順序相反,畫成不一樣的樹 ### backstracking (窮舉) (回溯) 例題:[zerojudge229 括號匹配問題](https://zerojudge.tw/ShowProblem?problemid=a229) ```cpp= vector<vector<int>> result; void backtrack_helper (路徑, 下一步選擇清單) { if ( 終止條件 ) result.add (路徑); return; for 下一步 in 選擇清單 做選擇 backtrack_helper (路徑, 下一步選擇清單) 撤銷選擇 } ``` ## 指標 指標相關 | | * | & | | -------- | -------- | -------- | | **宣告時** | 宣告指標 | 宣告參考變數(幾乎跟原本一模一樣的變數) |**非宣告時** | 取值符號 (取得指標變數指向的值) | 取址符號 (變數的記憶體位置) | **對參考變數的改變也會改到原本的變數喔!** ```cpp= * //為宣告指標用 e.g. int *ptr //這代表這是一個指向int的指標 & //為取址運算子 e.g. int *ptr = &a //a必須要是int struct p a; a.b ->表示指向的結構變數的內容 (*times1).p //與下者相等 times1->p -> ``` struct 裡的function (函式直接寫在struct內部) 範例: ```cpp= struct p { double h; double w; double BMI() { return this-> w/ (this->h * this->h); //this為物件本身的指標 但this也可以省略 } }; ``` ## 快速冪 計算$a^n$ 將n改為二進位,如果此時是1,就相乘 ```cpp= int power(int a, int n) { int ans = 1; for (; n; n/=2) { if (n&1) ans = ans*a; a = a*a; } } ``` ## Greedy ### 排程問題 只有一個->按照**結束時間**從早開始排 pf 參考2023資訊之芽手寫作業4 多個->結束時間+製作時間從小到大排序 ## 數學 ### 找質數 歐拉篩 ```cpp= bool isPrime[M]; vector<int> prime; fill(isPrime, isPrime+M+1, 1); for (int i=2; i<M; i++) { if (isPrime[i]) prime.push_back(i); for (int j: prime) { if (j*i > M) break; isPrime[i*j] = 0; if (i % j == 0) break; // 直到此質數是自己的因數,當跑到i/j時就會跑過了 } } ``` ### 找同餘? 模擬元 ## 酷酷待查的東東 ### enum ### next_premutation