1. 单链表
// e存储链表中的值,ne存储链表的next指针
int e[N], ne[N];
// 头结点head,结点编号idx
int head, idx;
// 初始化链表
void init() {
// 头结点初始化为NULL,要插入的第一个结点的指针记为0
head = -1, idx = 0;
}
// 表头插入结点
void insert_head(int x) {
e[idx] = x; // 插入结点赋值
ne[idx] = head; // next指针指向头结点
head = idx++; // 头结点指向当前节点
}
// 第k个数后面插入节点
void insert(int k, int x) {
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx++;
}
// 删除第k个插入的结点,删除头结点时需要特判
// 第k个结点的编号为k - 1
void remove(int k) {
ne[k] = ne[ne[k]];
}
2. 双链表
int e[N], l[N], r[N];
int m, idx;
// 初始化
void init() {
r[0] = 1; // 头结点指向尾结点
l[1] = 0; // 尾结点指向头结点
idx = 2; // 0和1被占用,初始编号从2开始
}
// 在编号为k的右边插入值为x的结点,维护前向和后向指针即可
void insert(int k, int x) {
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
r[k] = idx;
l[r[idx]] = idx;
idx++;
}
// 删除编号为k的结点
void remove(int k) {
r[l[k]] = r[k];
l[r[k]] = l[k];
}
3. 栈
int stk[N], tt = -1;
void push(int x) { stk[ ++ tt] = x; }
void pop() { tt -- ; }
bool empty() { return tt < 0; }
int query() { return stk[tt]; }
4. 队列
int q[N], hh, tt = -1;
void push(int x) { q[++tt] = x; }
void pop() { hh++; }
bool empty() { return hh > tt; }
int query() { return q[hh]; }
5. 单调栈
int a[N], n;
stack<int> stk;
int main() {
int n; cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 0; i < n; i ++ ) {
// 先入栈的不小于当前值的元素在之后一定不会被访问到,至少只会访问较小的当前元素
while (stk.size() && stk.top() >= a[i]) stk.pop();
if (stk.empty()) cout << -1 << ' ';
else cout << stk.top() << ' ';
stk.push(a[i]);
}
return 0;
}
6. 单调队列
// 滑动窗口
for (int i = 0; i < n; i ++ ) {
if (q.size() && i - q.front() >= k) q.pop_front(); // 若超出窗口范围则出队
while (q.size() && a[q.back()] > a[i]) q.pop_back(); // 先入队的较大的元素之后一定不会被访问
q.push_back(i);
if (i >= k - 1) cout << a[q.front()] << ' ';
}
7. KMP
// 模式串s,长度为m;模板串p,长度为n,下标均从1开始
char p[N], s[M];
int n, m, ne[N];
void kmp() {
// 构造ne数组
// ne[i] = j 表示 p[1:j] == p[i - j:i]
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 (s[i] == p[j + 1]) j ++ ;
if (j == n) {
// p到头了,匹配成功
}
}
}
8. Trie树
int son[N][26], cnt[N], idx;
string s;
void insert(string s) {
int p = 0; // 起始位置,不代表任何字符
for (int i = 0; i < s.size(); 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; i < s.size(); i ++ ) {
int u = s[i] - 'a';
if (!son[p][u]) return 0; // 查询操作中,不存在直接返回0
p = son[p][u];
}
return cnt[p];
}
class Trie {
public:
int cnt;
Trie* son[26] = {NULL};
Trie() {}
void insert(string str) {
Trie* p = this;
for (auto c : str) {
int u = c - 'a';
if (!p->son[u]) p->son[u] = new Trie();
p = p->son[u];
}
p->cnt ++ ;
}
int query(string str) {
Trie* p = this;
for (auto c : str) {
int u = c - 'a';
if (!p->son[u]) return 0;
p = p->son[u];
}
return p->cnt;
}
};
9. 并查集
// pa[x] = y x所属集合的根节点是y
int pa[N];
// 路径压缩的查找根节点操作
int find(int x) {
if (pa[x] != x) pa[x] = find(pa[x]);
return pa[x];
}
// 初始化,最开始每个结点自身构成一个集合,它的根节点是自己
for (int i = 1; i <= n; i++) pa[i] = i;
//合并两个集合
int r_a = find(a), r_b = find(b);
pa[r_a] = r_b;
10. 堆
// 数组模拟堆,siz是堆的大小
int h[N], siz;
// heapify
void down(int u) {
int t = u;
if (u * 2 <= siz && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t) {
swap(h[u], h[t]);
down(t);
}
}
void up(int u) {
while (u / 2 && h[u / 2] < h[u]) {
swap(h[u], h[u / 2]);
u /= 2;
}
}
// 建堆 编号从1开始
for (int i = siz / 2; i >= 1; i--) down(i);
11. hash表
散列hash
- 散列函数选择 h(x) = x mod k 时
- 拉链法:k一般选择质数较好
- 开放寻址法:散列表长度一般开数据范围的2~3倍,保证查询时不会因表被装满导致死循环
开放寻址法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5 + 3, null = 0x3f3f3f3f;
int h[N], n, x;
char op;
int find(int x) {
int k = (x % N + N) % N;
while (h[k] != null && h[k] != x) {
k ++ ;
if (k == N) k = 0;
}
return k;
}
int main() {
cin >> n;
memset(h, null, sizeof h);
while (n--) {
cin >> op >> x;
if (op == 'I') {
h[find(x)] = x;
} else if (op == 'Q') {
if (h[find(x)] == x) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
拉链法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 3;
// 链表存储映射地址相同的结点
int h[N], e[N], ne[N], idx;
int n, x;
char op;
void insert(int x) {
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
bool query(int x) {
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
while (n--) {
cin >> op >> x;
if (op == 'I') {
insert(x);
} else if (op == 'Q') {
if (query(x)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
字符串hash
- h[i]表示字符串前i个字符组成的前缀的哈希值
- $p[i] = P^i$,提前算好方便之后计算
- $P$一般取131或者13331,哈希冲突概率最小
- 类型取unsigned long long, 等价于模2^64
- 计算字串S[l:r]的哈希值公式:$h[l:r] = (h[r] - h[l - 1] * P ^ {r - l + 1}) \% Q, Q = 2 ^ {64}$
#include <iostream>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5 + 10, P = 131;
int h[N], p[N], n, m;
char s[N];
int get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
int main() {
cin >> n >> m >> s + 1;
p[0] = 1;
for (int i = 1; i <= n; i ++ ) {
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + s[i];
}
int l1, l2, r1, r2;
while (m -- ) {
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
tql