单链表
利用数组模拟链表,相当于链式前向星。head表示头结点的下标,e[i]表示i节点的值,ne[i]表示i节点指向的下一个节点的下标,idx表示当前可用的最后一个下标;因为idx从0开始不是从1开始,所以题目输入的k在用的时候需要-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> using namespace std;const int N = 100010 ;int head, e[N], ne[N], idx;void Init () { head = -1 ; idx = 0 ; } void add_to_head (int x) { e[idx] = x; ne[idx] = head; head = idx; idx++; } void add (int k, int x) { e[idx] = x; ne[idx] = ne[k]; ne[k] = idx; idx++; } void delete_k (int k) { ne[k] = ne[ne[k]]; } int main () { int n; cin >> n; Init (); while (n--) { char x; cin >> x; int k, val; if (x == 'H' ) { cin >> val; add_to_head (val); } else if (x == 'I' ) { cin >> k >> val; add (k - 1 , val); } else if (x == 'D' ) { cin >> k; if (!k) head = ne[head]; else delete_k (k - 1 ); } } for (int i = head; i != -1 ; i = ne[i]) cout << e[i] << " " ; return 0 ; }
双链表
根据单链表的经验,双链表只需要添加指向前一个结点的数组即可;这里我们省略head和tail,让0是左端点,1是右端点,那么idx就从2开始;在初始的时候head指向tail,tail指向head,因为前后节点都知道,所以只需要写一个插入函数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> using namespace std;const int N = 100010 ;int e[N], le[N], re[N], idx;void Init () { le[1 ] = 0 ; re[0 ] = 1 ; idx = 2 ; } void Insert (int k, int x) { e[idx] = x; le[idx] = k; re[idx] = re[k]; le[re[k]] = idx; re[k] = idx; idx++; } void Delete (int k) { re[le[k]] = re[k]; le[re[k]] = le[k]; } int main () { int m; cin >> m; Init (); while (m--) { string s; cin >> s; int x, k; if (s == "L" ) { cin >> x; Insert (0 , x); } else if (s == "R" ) { cin >> x; Insert (le[1 ], x); } else if (s == "D" ) { cin >> k; Delete (k + 1 ); } else if (s == "IL" ) { cin >> k >> x; Insert (le[k + 1 ], x); } else if (s == "IR" ) { cin >> k >> x; Insert (k + 1 , x); } } for (int i = re[0 ]; i != 1 ; i = re[i]) cout << e[i] << " " ; return 0 ; }
表达式求值
建两个栈,一个存数字,一个存运算符
需要注意:
遇到一个字符是数字,需要判断后面是不是接着是数字,将字符串转换成数字
结束条件之一是没有剩余的运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <stack> #include <unordered_map> #include <string> using namespace std;const int N = 1e5 + 10 ;stack<int > num; stack<char > sg; unordered_map<char , int > mp{ {'+' , 1 }, {'-' , 1 }, {'*' , 2 }, {'/' , 2 } }; void eval () { int a = num.top (); num.pop (); int b = num.top (); num.pop (); char op = sg.top (); sg.pop (); int r = 0 ; if (op == '+' ) r = b + a; else if (op == '-' ) r = b - a; else if (op == '*' ) r = b * a; else r = b / a; num.push (r); } int main () { string s; cin >> s; for (int i = 0 ; i < s.size (); i++) { if (isdigit (s[i])) { int j = i, x = 0 ; while (j < s.size () && isdigit (s[j])) { x = x * 10 + s[j] - '0' ; j++; } num.push (x); i = j - 1 ; } else if (s[i] == '(' ) { sg.push (s[i]); } else if (s[i] == ')' ) { while (sg.top () != '(' ) eval (); sg.pop (); } else { while (sg.size () && mp[sg.top ()] >= mp[s[i]]) eval (); sg.push (s[i]); } } while (sg.size ()) eval (); cout << num.top (); return 0 ; }
单调栈
解析 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int st[N], tt = 0 ;int main () { int n; cin >> n; while (n--) { int x; cin >> x; while (tt && st[tt] >= x) tt--; if (!tt) cout << -1 << " " ; else cout << st[tt] << " " ; st[++tt] = x; } return 0 ; }
单调队列
首先根据题意我们知道可以用队列模拟滑动窗口,但是如果直接暴力的话,时间复杂度是O(nk),显然会超时,这时候就可以用单调队列优化,即将不需要的数字删去,看最后队列中的数是否具有单调性,如果有,就可以用单调队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;const int N = 1e6 + 10 ;int q[N], a[N];int main () { std::ios::sync_with_stdio (0 ); std::cin.tie (0 ); int n, k; cin >> n >> k; for (int i = 0 ; i < n; i++) cin >> a[i]; int hh = 0 , tt = -1 ; for (int i = 0 ; i < n; i++) { if (hh <= tt && i - k + 1 > q[hh]) ++hh; while (hh <= tt && a[i] <= a[q[tt]]) --tt; q[++tt] = i; if (i >= k - 1 ) cout << a[q[hh]] << " " ; } cout << '\n' ; hh = 0 , tt = -1 ; for (int i = 0 ; i < n; i++) { if (hh <= tt && i - k + 1 > q[hh]) ++hh; while (hh <= tt && a[i] >= a[q[tt]]) --tt; q[++tt] = i; if (i >= k - 1 ) cout << a[q[hh]] << " " ; } return 0 ; }
KMP算法
暴力,i指针遍历s字符串,j指针遍历p字符串;如果遍历不成功i就会回退,时间复杂度是O(mn)会超时
KMP优化,利用next数组,让i指针不回退,如果不成功就让j根据next数组跳转,next跳转的位置前面的字符串应该与当前匹配到的位置前面的字符串一致;时间复杂度O(m
+ n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> using namespace std;const int N = 1e5 + 10 , M = 1e6 + 10 ;char s[M], p[N];int ne[N];int main () { int n, m; cin >> n >> p + 1 >> m >> s + 1 ; for (int i = 2 , j = 0 ; i <= n; i++) { while (j && p[i] != p[j + 1 ]) j = ne[j]; if (p[i] == p[j + 1 ]) j++; ne[i] = j; } for (int i = 1 , j = 0 ; i <= m; i++) { while (j && s[i] != p[j + 1 ]) j = ne[j]; if (p[j + 1 ] == s[i]) j++; if (j == n) { cout << i - n << " " ; j = ne[j]; } } return 0 ; }
Trie树
高效存储和查找字符串集合的数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <string> using namespace std;const int N = 1e5 + 10 ;int son[N][26 ], cnt[N], idx;void Insert (string s) { int p = 0 ; for (int i = 0 ; s[i]; i++) { int u = s[i] - 'a' ; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } cnt[p]++; } int Query (string s) { int p = 0 ; for (int i = 0 ; s[i]; i++) { int u = s[i] - 'a' ; if (son[p][u]) p = son[p][u]; else return 0 ; } return cnt[p]; } int main () { char x; string s; int n; cin >> n; while (n--) { cin >> x >> s; if (x == 'I' ) Insert (s); else cout << Query (s) << endl; } return 0 ; }
最大异或对
利用Trie树,从高位到低位存储每一个数的二进制形式;利用遍历,尽量找每一位都不一样的数,从而找到每一个数与其他数异或中的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> using namespace std;const int N = 1e5 + 10 , M = 31 * N;int a[N], son[M][2 ], idx;void Insert (int x) { int p = 0 ; for (int i = 30 ; ~i; i--) { int u = x >> i & 1 ; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } } int Query (int x) { int p = 0 ; int res = 0 ; for (int i = 30 ; ~i; i--) { int u = x >> i & 1 ; if (son[p][!u]) { p = son[p][!u]; res = 2 * res + 1 ; } else { p = son[p][u]; res = 2 * res + 0 ; } } return res; } int main () { int n; cin >> n; for (int i = 0 ; i < n; i++) { cin >> a[i]; Insert (a[i]); } int res = 0 ; for (int i = 0 ; i < n; i++) res = max (res, Query (a[i])); cout << res; return 0 ; }
并查集
将两个集合合并
询问两个元素是否在同一个集合中
用p数组表示该集合中元素的父节点,当p[i] == i的时候,i是根节点
合并集合:让两个数的根节点合并
是否在同一个集合:判断根节点是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int p[N];int find (int x) { if (x != p[x]) p[x] = find (p[x]); return p[x]; } void Merge (int a, int b) { p[find (a)] = find (b); } string Query (int a, int b) { if (find (a) == find (b)) return "Yes" ; else return "No" ; } int main () { char x; int a, b; int n, m; cin >> n >> m; for (int i = 1 ; i <= n; i++) p[i] = i; while (m--) { cin >> x >> a >> b; if (x == 'M' ) Merge (a, b); else cout << Query (a, b) << endl; } return 0 ; }
食物链
根据分析,如果将整个食物链连成一棵树,那么到根节点距离
除3余0,就跟根节点为同类
除3余1,就被根节点吃
除3余2,就吃根节点
返回值为bool的函数,一定要保证每条路径有返回值
找到x,
y的根节点,如果根节点不一样,则跟前面的话不可能冲突,所以一定是真话,将二者的关系加上即可;如果一样就通过到根节点的距离除3的余数判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <iostream> using namespace std;const int N = 5e4 + 10 , M = 3 ;int d[N], fa[N];int find (int x) { if (x != fa[x]) { int t = find (fa[x]); d[x] += d[fa[x]]; fa[x] = t; } return fa[x]; } bool D1 (int x, int y) { int fx = find (x), fy = find (y); if (fx == fy && (d[y] - d[x] + M) % M) return false ; else if (fx != fy) { fa[fx] = fy; d[fx] = d[y] - d[x]; return true ; } return true ; } bool D2 (int x, int y) { int fx = find (x), fy = find (y); if (fx == fy && (d[y] - d[x] - 1 + M) % M) return false ; else if (fx != fy) { fa[fx] = fy; d[fx] = (d[y] - 1 - d[x] + M) % M; return true ; } else return true ; } int main () { int n, k; cin >> n >> k; int cnt = 0 ; for (int i = 1 ; i <= n; i++) { fa[i] = i; d[i] = 0 ; } while (k--) { int D, x, y; cin >> D >> x >> y; if (x > n || y > n) { cnt++; continue ; } int fx = find (x), fy = find (y); if (D == 1 ) if (!D1 (x, y)) cnt++; if (D == 2 ) if (!D2 (x, y)) cnt++; } cout << cnt; return 0 ; }
堆排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int n, m;int a[N];void down (int p, int n) { int c = p * 2 ; while (c <= n) { if (c + 1 <= n && a[c] > a[c + 1 ]) c++; if (a[c] < a[p]) { swap (a[c], a[p]); p = c; c = p * 2 ; } else break ; } } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> a[i]; int cnt = n; for (int i = n / 2 ; i; i--) down (i, n); while (m--) { cout << a[1 ] << " " ; a[1 ] = a[cnt--]; down (1 , cnt); } }
模拟散列表
链地址法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 3 ;int h[N], ne[N], e[N], idx;void Insert (int x) { int k = (x % N + N) % N; e[idx] = x; ne[idx] = h[k]; h[k] = idx++; } string Query (int x) { int k = (x % N + N) % N; for (int i = h[k]; i != -1 ; i = ne[i]) { if (e[i] == x) return "Yes" ; } return "No" ; } int main () { memset (h, -1 , sizeof h); int n; cin >> n; while (n--) { char op; cin >> op; if (op == 'I' ) { int x; cin >> x; Insert (x); } else if (op == 'Q' ) { int x; cin >> x; cout << Query (x) << endl;; } } return 0 ; }
开放寻址法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <cstring> using namespace std;const int N = 2e5 + 3 ;const int null = 0x3f3f3f3f ;int h[N];int find (int x) { int t = (x % N + N) % N; while (h[t] != null && h[t] != x) { t++; if (t == N) t = 0 ; } return t; } int main () { memset (h, null, sizeof h); int n; cin >> n; while (n--) { char op; int x; cin >> op >> x; if (op == 'I' ) { h[find (x)] = x; } else { if (h[find (x)] == null) cout << "No" << endl; else cout << "Yes" << endl; } } return 0 ; }
字符串哈希
将字符串假设成为p进制的数,经过实验发现,当p=131或者p=1331的时候,可以当做没有冲突;然后就可以将字符串(p进制)转化成十进制数字,用p数组表示p的n次方,h数组存储前n个字符组成的字符串转换成的数字;可以利用前缀和的思想,发现h[s[l,
r] ]= h[r] - h[l - 1] * p[r - l + 1];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> using namespace std;const int N = 1e5 + 10 ;const int P = 131 ;typedef unsigned long long ULL;ULL h[N], p[N]; char s[N]; ULL Query (int l, int r) { return h[r] - h[l - 1 ]* p[r - l + 1 ]; } int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> s[i]; p[0 ] = 1 , h[0 ] = 0 ; for (int i = 1 ; i <= n; i++) { p[i] = p[i - 1 ] * P; h[i] = h[i - 1 ] * P + s[i]; } while (m--) { int l1, l2, r1, r2; cin >> l1 >> r1 >> l2 >> r2; if (Query (l1, r1) == Query (l2, r2)) cout << "Yes" << endl; else cout << "No" << endl; } return 0 ; }