AcWing Data Structure + STL
Linked List and Adjacency List
Mocking with structs: slower
struct Node
{
int val;
Node *next;
};
Simulate using an array:
int head; // 头节点的下标
int e[N]; // 值
int ne[N]; // 指向的下一个值
int idx; // 存储当前已经使用到哪个点
Singly linked list (static linked list)
Title: 826. Singly Linked List - AcWing Question Bank
Arrays simulate singly linked lists:
#include <iostream>
using namespace std;
const int N = 100010;
// head - 头节点的下标
// e[i] - 节点 i 的值
// ne[i] - 节点 i 的下一个节点的下标
// idx - 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将 x 插到头结点
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx++;
}
// 将 x 插到下标 k 的点后面
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
// 将下标是 k 的点后面的点删除
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m; cin >> m;
init();
while (m --)
{
int k, x;
char op;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head]; // 删除头节点
remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
// 遍历输出
for (int i = head; i != -1; i = ne[i]) cout << e[i] << " ";
cout << endl;
return 0;
}
Double linked list (static linked list)
Title: AcWing 827. Double Linked List - AcWing
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int e[N], l[N], r[N], idx;
// 初始化双链表
void init()
{
// 0 表示左端点,1 表示右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在 k 下标的节点右边插入 x
void add(int k, int x)
{
e[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx;
idx++;
}
// 在 k 下标的节点左边插入 x
// add(l[k], x);
// 删除 k 下标的节点
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main()
{
int m;
cin >> m;
init();
while (m--)
{
int k, x;
string op; cin >> op;
if (op == "L")
{
cin >> x;
add(0, x); // 0 左端点
}
else if (op == "R")
{
cin >> x;
add(l[1], x); // 1 右端点
}
else if (op == "D")
{
cin >> k;
remove(k + 1); // k - 1 + 2
}
else if (op == "IL")
{
cin >> k >> x;
add(l[k + 1], x);
}
else if (op == "IR")
{
cin >> k >> x;
add(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i])
cout << e[i] << ' ';
return 0;
}
Stacks and Queues
simulation stack
Note
t = 0
ort = -1
mainly affects the method ofempty()
judging stack empty
t = 0
// s - 数组模拟栈,t - 栈顶指针
int s[N], t = 0; // *
// 向栈顶插入一个数 x
void push(int x)
{
s[++t] = x;
}
// 从栈顶弹出一个数
void pop()
{
t--;
}
// 判断栈是否为空
bool empty()
{
return t <= 0; // *
}
// 查询栈顶元素
int query()
{
return s[t];
}
t = -1
:
// s - 数组模拟栈,t - 栈顶指针
int s[N], t = -1; // *
// 向栈顶插入一个数 x
void push(int x)
{
s[++t] = x;
}
// 从栈顶弹出一个数
void pop()
{
t--;
}
// 判断栈是否为空
bool empty()
{
return t < 0; // *
}
// 查询栈顶元素
int query()
{
return s[t];
}
Title: 828. Simulation Stack - AcWing Question Bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// implement stack
int main()
{
int m; cin >> m;
while (m--)
{
int x;
string op; cin >> op;
if (op == "push")
{
cin >> x;
push(x);
}
else if (op == "pop")
{
pop();
}
else if (op == "empty")
{
cout << (empty() ? "YES" : "NO") << endl;
}
else if (op == "query")
{
cout << query() << endl;
}
}
return 0;
}
mock queue
hh = 0
, tt = -1
: recommended
// hh - 队头位置, tt - 队尾位置
int q[N], hh = 0, tt = -1;
// 向队尾插入一个 x
void push (int x)
{
q[++tt] = x; // *
}
// 从队头弹出一个数
void pop ()
{
hh++;
}
// 判断队列是否为空
bool empty ()
{
return hh > tt; // *
}
// 查询队头元素
int query ()
{
return q[hh];
}
hh = 0
,tt = 0
:
// hh - 队头位置, tt - 队尾位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个 x
void push (int x)
{
q[tt++] = x; // *
}
// 从队头弹出一个数
void pop ()
{
hh++;
}
// 判断队列是否为空
bool empty ()
{
return hh >= tt; // *
}
// 查询队头元素
int query ()
{
return q[hh];
}
Topic: 829. Simulation Queue - AcWing Question Bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// implement queue ...
int main()
{
int m; cin >> m;
while (m --)
{
int x;
string op; cin >> op;
if (op == "push")
{
cin >> x;
push(x);
}
else if (op == "pop")
{
pop();
}
else if (op == "empty")
{
cout << (empty() ? "YES" : "NO") << endl;
}
else if (op == "query")
{
cout << query() << endl;
}
}
return 0;
}
expression evaluation
Title: 3302. Expression Evaluation - AcWing Question Bank
#include <iostream>
#include <unordered_map>
#include <stack>
using namespace std;
stack<int> num; // 存储数字的栈
stack<char> op; // 存储操作符的栈
// 优先级表
unordered_map<char, int> h {
{
'+', 1}, {
'-', 1}, {
'*', 2}, {
'/', 2}};
// 求值
void eval()
{
// 第一个操作数
int a = num.top(); num.pop();
// 第二个操作数
int b = num.top(); num.pop();
// 运算符
char p = op.top(); op.pop();
// 计算结果并入栈
int res = 0;
if (p == '+') res = b + a;
if (p == '-') res = b - a;
if (p == '*') res = b * a;
if (p == '/') res = b / a;
num.push(res);
}
int main()
{
string exp; cin >> exp;
for (int i = 0; i < exp.size(); i++)
{
// 扫描到数字
if (isdigit(exp[i]))
{
int x = 0, j = i;
while (j < exp.size() && isdigit(exp[j]))
{
x = (x * 10 + exp[j]) - '0';
j++;
}
num.push(x);
i = j - 1;
}
// '(' 直接入栈
else if (exp[i] == '(')
{
op.push(exp[i]);
}
// ')' 计算内容直到遇到匹配的 '('
else if (exp[i] == ')')
{
while (op.top() != '(') eval();
op.pop(); // 将 '(' 出栈
}
else
{
// 待入栈运算符优先级低(或相等)则先计算
while (op.size() && h[op.top()] >= h[exp[i]]) eval();
op.push(exp[i]);
}
}
// 计算栈中剩余的
while (op.size()) eval();
cout << num.top() << endl;
return 0;
}
monotonic stack
The monotonic stack is used to find the first number on the left of each number that is smaller (larger) than it .
Title: 830. Monotonic Stack - AcWing Question Bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int stk[N], tt = 0;
int main()
{
int n; cin >> n;
for (int i = 0; i < n; i++)
{
int x; cin >> x;
while (tt && stk[tt] >= x) tt--;
cout << (tt ? stk[tt] : -1) << " ";
stk[++tt] = x;
}
return 0;
}
monotonic queue
Monotonic queues are used to find the maximum (minimum) value in a sliding window .
Title: 154. Sliding Window - AcWing Question Bank
#include <cstdio>
const int N = 1e6 + 10;
int a[N], q[N]; // 队列中存的是下标
int main()
{
int n, k;
scanf("%d %d", &n, &k);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
int hh = 0, tt = -1; // 数组模拟队列
// 单调递增队列
for (int i = 0; i < n; i++)
{
// i - k + 1 滑动窗口头位置, q[hh] 窗口最小值的位置
if (hh <= tt && i - k + 1 > q[hh]) hh++;
while (hh <= tt && a[i] <= a[q[tt]] ) tt--;
q[++tt] = i;
if (i + 1 >= k) printf("%d ", a[q[hh]]);
}
puts("");
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 + 1 >= k) printf("%d ", a[q[hh]]);
}
return 0;
}
kmp
slightly. . Make up later. .
Trie tree
Trie tree is a data structure for fast storage and lookup of collections of strings.
// son[][] 存储当前节点的子节点的位置,分支最多 26 条
// cnt[] - 以当前点结尾的字符串个数(同时起标记作用)
// idx - 当前要插入的节点是第几个,每创建一个节点值 + 1
int son[N][26], cnt[N], idx;
void insert(char *str) // 插入字符串
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p] ++ ;
}
int query(char *str) // 查询字符串出现次数
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
Trie String Statistics
Title: 835. Trie String Statistics - AcWing Question Bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// implement trie
int main()
{
int n; cin >> n;
char str[N];
while (n -- )
{
char op;
cin >> op >> str;
if (op == 'I') insert(str);
else if (op == 'Q') cout << query(str) << endl;
}
return 0;
}
Maximum XOR Pair
Topic: 143. Maximum XOR Pair - AcWing Question Bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 31 * N;
int a[N];
int son[M][2], idx;
void insert(int x)
{
int p = 0;
// 从高往低
for (int i = 30; i >= 0; i--)
{
int u = x >> i & 1; // 取 x 二进制第 i 位的值A
if (!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x)
{
int p = 0, res = 0;
for (int i = 30; i >= 0; i--)
{
int u = x >> i & 1;
if (son[p][!u])
{
p = son[p][!u];
res = res * 2 + !u;
}
else
{
p = son[p][u];
res = res * 2 + u;
}
}
return res;
}
int main()
{
int n; cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int res = 0;
for (int i = 0; i < n; i++)
{
insert(a[i]);
int t = query(a[i]);
res = max(res, a[i] ^ t);
}
cout << res << endl;
return 0;
}
And lookup
And lookup:
- Merge the two collections
- Ask if two elements are in a set
Rationale: Each set is represented by a tree. The number of the root of the tree is the number of the entire collection. Each node stores its parent node, p[x]
denoting the parent node of x.
Question 1: How to judge the root of the tree: if (p[x] == x)
Question 2: If you want to find the collection number of x: while (p[x] != x) x = p[x];
Question 3: How to merge two collections, which p[x]
is the collection number of x and p[y]
the collection number of y:p[x] = y
merge collection
Naive union check:
// 存储每个节点的祖宗节点, 当 p[x] == x, 表示这个数就是祖宗节点
int p[N];
// 返回 x 的祖宗节点 + 路径压缩
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 将编号为 a 和 b 的两个数所在的集合合并
void merge(int a, int b)
{
p[find(a)] = find(b);
}
// 编号 a 和 b 的两个数是否在同一个集合中
bool isSame(int a, int b)
{
return find(a) == find(b);
}
// 初始化,假定节点的编号是 1 ~ n
void init()
{
for (int i = 1; i <= n; i++) p[i] = i;
}
Template question: 836. Merge collections - AcWing question bank
Reference: AcWing 836. Basics_merge set_merge setjava_python_c++ - AcWing
Path compression:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// implement union find
int main()
{
int n, m;
cin >> n >> m;
// 初始化
for (int i = 1; i <= n; i++) p[i] = i;
while (m --)
{
char op;
int a, b;
cin >> op >> a >> b;
if (op == 'M') merge(a, b);
else puts(isSame(a, b) ? "Yes" : "No");
}
return 0;
}
The number of midpoints in connected blocks
Template: maintain the union of size
// p[] 存储每个点的祖宗节点
// size[] 只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
int p[N], size[N];
// 返回 x 的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是 1 ~ n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并 a 和 b 所在的两个集合
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
Template question: 837. The number of midpoints in connected blocks-AcWing question bank
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// p[i] i 的祖先节点
// nums[i] i 的连通块中点的数量
int p[N], nums[N];
// 查找 x 的祖宗节点 + 路径压缩
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int n, m;
cin >> n >> m;
// 初始化并查集
for (int i = 1; i <= n; i++)
{
p[i] = i;
nums[i] = 1; // 默认只有一个点
}
while (m--)
{
int a, b;
char op[2];
cin >> op;
if (op[0] == 'C')
{
cin >> a >> b;
if (find(a) == find(b)) continue; // a b 已经在一个集合中
nums[find(b)] += nums[find(a)];
p[find(a)] = find(b); // 合并
}
else if (op[1] == '1')
{
cin >> a >> b;
puts(find(a) == find(b) ? "Yes" : "No");
}
else if (op[1] == '2')
{
cin >> a;
cout << nums[find(a)] << endl;
}
}
return 0;
}
heap
Common operations on the heap:
- insert a number
- Find the smallest value in a set
- remove minimum
- remove any element
- Modify any element
Write a heap by hand:
// 1. 插入一个数
heap[++size] = x;
up(size);
// 2. 求集合当中的最小值
heap(1);
// 3. 删除最小值
heap[1] = heap[size];
size--;
down(1);
// 4. 删除任意一个元素
heap[k] = heap[size];
size--;
down(k);
up(k);
// 5. 修改任意一个元素
heap[k] = x;
down(k);
up(k);
heap sort
Template question: 838. Heap sorting - AcWing question bank
Details: In order to avoid dealing with complex boundary problems, the subscript of the h array starts from 1
and2i
is the subscript of the left subtree and2i + 1
the subscript of the right subtree
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// h - 满足堆性质的数组
int h[N], siz;
// 从 u 往下调整,使得数组满足堆的性质
void down(int u)
{
// t 存储三个节点中存在最小的下标,初始化为当前节点 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;
// t 进行了更新操作,则交换数组中的值并重新从当前 t 开始调整堆
if (t != u)
{
swap(h[t], h[u]);
down(t);
}
}
int main()
{
int n, m;
cin >> n >> m;
// 读入乱序元素
for (int i = 1; i <= n; i++) cin >> h[i];
siz = n; // 初始化 size,表示堆里有 n 个元素
// 把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉
for (int i = n / 2; i; i--) down(i);
while (m --)
{
cout << h[1] << " "; // 堆顶,最小值
h[1] = h[siz--]; // 将最后一个元素挪到堆顶(相当于删除了最值元素)
down(1); // 从头节点开始重新排列一遍,确保头节点是最小的
}
return 0;
}
mock heap TODO
hash table
Hash table:
- Storage structure: open addressing method, zipper method
- string hash
Analog hash table
840. Analog Hash Table - AcWing Question Bank
Zipper method:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003; // 大于数据范围的第一个质数
int h[N], e[N], ne[N], idx;
void insert (int x)
{
// x % N + N 保证结果必为正数
int k = (x % N + N) % N;
e[idx] = x, ne[idx] = h[k], h[k] = idx++;
}
bool find (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()
{
int n; cin >> n;
memset(h, -1, sizeof h);
while (n --)
{
char op;
int x;
cin >> op >> x;
if (op == 'I') insert(x);
else puts(find(x) ? "Yes" : "No");
}
return 0;
}
Open addressing method:
#include <iostream>
#include <string.h>
using namespace std;
// 开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N = 200003; // 大于数据范围的第一个质数
const int null = 0x3f3f3f3f; // 表示不存在数据,这个数不能在 x 范围内
int h[N];
// 寻找 x 存在的位置(已存在)或目标放置位置(不存在)
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()
{
int n; cin >> n;
memset(h, 0x3f, sizeof h);
while (n --)
{
char op;
int x;
cin >> op >> x;
int k = find(x);
if (op == 'I') h[k] = x;
else puts(h[k] != null ? "Yes" : "No");
}
return 0;
}
string hash
Title: 841. String Hashing - AcWing Question Bank
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5 + 5;
// P = 131 或 13331 Q=2^64,在 99% 的情况下不会出现冲突
const int P = 131; // 131 13331 是经验值
// h[i] 字符串的哈希前缀和
ULL h[N], p[N];
// h[i] 前 i 个字符的 hash 值
// 字符串变成一个 P 进制数字,体现了字符 + 顺序,需要确保不同的字符串对应不同的数字
// 使用场景:两个字符串的子串是否相同
ULL query(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int n, m;
cin >> n >> m;
string x;
cin >> x;
p[0] = 1;
h[0] = 0;
// 前缀和
for (int i = 0; i < n; i++)
{
p[i + 1] = p[i] * P; // 预处理 P 进制
h[i + 1] = h[i] * P + x[i]; // 前缀和求整个字符串的哈希值
}
while (m--)
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
puts((query(l1, r1) == query(l2, r2) ? "Yes" : "No"));
}
cout << p << endl;
return 0;
}
STL
Commonly used containers in STL:
- vector, variable-length array, the idea of doubling (reduce the number of times to apply for space, improve efficiency)
- pair, store a two-tuple
- string, string
- queue, queue
- priority_queue, priority queue
- stack, stack
- deque, double-ended queue
- set, map, multiset, multimap, based on balanced binary tree (red-black tree), dynamically maintain ordered sequence
- unordered_set, unordered_map, unordered_multiset, unordered_multimap,哈希表
- bitset, press bit
vector
vector,动态数组
size()
empty()
clear()
front() / back()
push_back() / pop_back()
begin() / end()
[] 支持像数组一样随机存储
支持比较运算,按字典序
Initialization of vecotr:
// 定义时初始化,容量为 10,初始值为 -1
vecotr<int> a(10, -1);
// 定义完再插入数据
vector<int> a;
for (int i = 0; i < 10; i++) a.push_back(i);
The traversal of vector:
// 遍历 1
for (int i = 0; i < a.size(); i++) cout << a[i] << ' ';
cout << endl;
// 遍历 2 - 迭代器,遍历时获取的是指针
for (auto i = a.begin(); i != a.end(); i++) cout << *i << ' ';
cout << endl;
// 遍历 3
for (auto x : a) cout << x << ' ';
cout << endl;
Comparison operators for vector:
// [3 3 3 3] < [4 4 4]
vector<int> a(4, 3), b(3, 4);
if (a < b) puts("a < b");
pair
pair<int, int>,二元对
first 第一个元素
second 第二个元素
支持比较运算,以 first 为第一关键字,second 为第二关键字(字典序)
The same effect can be achieved by writing a structure by yourself. The advantage of using this is to save code
Initialization of pair:
// 使用 make_pair
pair<int, int> p1 = make_pair(1, 2);
// 直接使用 {}
pair<int, string> p2 = {
20, "abc"};
Use pair to implement ternary pairs:
pair<int, pair<int, int>> p = {
1, {
2, 3}};
string
string,字符串
size() / length() 返回字符串的长度
empty()
clear()
substr()
c_str()
string a = "abcde";
cout << a.substr(1, 2) << endl; // bc
cout << a.substr(1) << endl; // bcde
string a = "hello ";
a += "world";
cout << a << endl; // hello world
printf("%s\n", a.c_str()); // 使用 printf 需要使用 c_str() 方法
queue
queue,队列
size()
empty()
push() 向队尾插入一个元素
pop() 弹出队头元素
front() 返回队头元素
back() 返回队尾元素
Note that the queue does not have clear()
a function , if you want the situation, you can directly reconstruct a queue
queue<int> q;
q = queue<int>(); // 相当于清空 queue
priority_queue
priority_queue,优先队列(默认大根堆)
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
priority_queue has no
clear()
Define a small root heap:
priority_queue<int, vector<int>, greater<int>> heap;
Other tricks: use it when inserting an element -x
, and add another when taking it out -
, which is also equivalent to a small root pile:
priority_queue<int> heap;
heap.push(-1);
heap.push(-2);
heap.push(-3);
cout << -heap.top() << endl; // 1
stack
stack,栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
therefore
deque,双端队列
size()
empty()
clear()
front() / back()
push_back() / pop_back()
push_front() / pop_front()
begin() / end()
[]
There are many deque methods, but the efficiency is relatively slow, and not many are used
set
set / multiset
size()
empty()
clear()
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入一个数 x,删除所有 x,复杂度 O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound() 最小上界,返回 >= x 的最小的数的迭代器
upper_bound() 最大下界,返回 >x 的最小的数的迭代器
map
map / multimap
insert() 参数是一个 pair
erase() 参数是 pair 或迭代器
find()
[] O(logn)
lower_bound() / upper_bound()
Map usage is similar to arrays:
map<string, int> m;
m["a"] = 1;
cout << m["a"] << endl;
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和前面的 map set 类似,但是增删改查的时间复杂度是 O(1)
但是不支持 lower_bound() / upper_bound(),迭代器的 ++, --
bitset
bitset,压位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回多少个 1
any() 判断是否至少有一个 1
none() 判断是否全为 0
set() 把所有位置成 1
set(k, v) 将第 k 位变成 v
reset() 将所有位置成 0
flip() 等价于 ~
flip(k) 第 k 位取反