目录
1、Trie
1.1 模板
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
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];
}
1.2 例题
题目来源:AcWing
题目链接:AcWing 835. Trie字符串统计
代码展示:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int son[N][26], cnt[N], idx;
char str[N];
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];
}
int main()
{
int n;
cin >> n;
while (n -- )
{
char op[2];
scanf("%s%s", op, str);
if (op[0] == 'I') insert(str);
else cout << query(str) << endl;
}
return 0;
}
题目来源:AcWing 题目链接:AcWing 143. 最大异或对 代码展示:
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 3100010;
int a[N], son[M][2], idx;
void insert(int x)
{
int p = 0;
for (int i = 30; i >= 0; i --)
{
int u = x >> i & 1;
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
}
int search(int x)
{
int p = 0, res = 0;
for (int i = 30; i >= 0; i -- )
{
int u = x >> i & 1;
if (son[p][!u])
{
res += 1 << i;
p = son[p][!u];
}
else p = son[p][u];
}
return res;
}
int main()
{
int n, res = 0;
cin >> n;
for (int i = 0; i < n; i ++ )
{
scanf("%d", &a[i]);
insert(a[i]);
}
for (int i = 0; i < n; i ++ )
{
res = max(res, search(a[i]));
}
cout << res << endl;
return 0;
}
2、并查集
2.1 模板
(1)朴素并查集:
int p[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;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回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);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
2.2 例题
题目来源:AcWing
题目链接:AcWing 836. 合并集合
代码展示:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int p[N];
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;
while (m -- )
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M') p[find(a)] = find(b);
else
{
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
题目来源:AcWing
题目链接:AcWing 837. 连通块中点的数量
代码展示:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int p[N], cnt[N];
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;
cnt[i] = 1;
}
while (m -- )
{
string op;
cin >> op;
if (op == "C")
{
int a, b;
cin >> a >> b;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
cnt[b] += cnt[a];
}
}
else if (op == "Q1")
{
int a, b;
cin >> a >> b;
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
int a;
cin >> a;
cout << cnt[find(a)] << endl;
}
}
return 0;
}
题目来源:AcWing
题目链接:AcWing 240. 食物链
代码展示:
#include <iostream>
using namespace std;
const int N = 500010;
int p[N], d[N];
int find(int x)
{
if (p[x] != x)
{
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main()
{
int n, k, res = 0;
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (k -- )
{
int t, x, y;
cin >> t >> x >> y;
if (x > n || y > n) res ++ ;
else
{
int px = find(x), py = find(y);
if (t == 1)
{
if (px == py && (d[x] - d[y]) % 3) res ++;
else if (px != py)
{
p[px] = py;
d[px] = d[y] - d[x];
}
}
else
{
if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
else if (px != py)
{
p[px] = py;
d[px] = d[y] + 1 - d[x];
}
}
}
}
cout << res << endl;
return 0;
}
3、堆
3.1 模板
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
3.2 例题
题目来源:AcWing
题目链接:AcWing 838. 堆排序
代码展示:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N], cnt;
void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] <= h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] <= h[t]) t = u * 2 + 1;
if (t != u)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
cnt = n;
for (int i = n / 2; i; i -- ) down(i);
while (m -- )
{
cout << h[1] << " ";
h[1] = h[cnt];
cnt -- ;
down(1);
}
return 0;
}
题目来源:AcWing
题目链接:AcWing 839. 模拟堆
代码展示:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N], ph[N], hp[N], cnt, m;
void heap_swap(int a, int b)
{
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (t != u)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int main()
{
int n;
cin >> n;
while (n -- )
{
string op;
int k, x;
cin >> op;
if (op == "I")
{
cin >> x;
cnt ++ ;
m ++ ;
ph[m] = cnt, hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if (op == "PM") cout << h[1] << endl;
else if (op == "DM")
{
// h[1] = h[cnt];
heap_swap(1, cnt);
cnt -- ;
down(1);
}
else if (op == "D")
{
cin >> k;
k = ph[k];
// h[k] = h[cnt];
heap_swap(k, cnt);
cnt -- ;
down(k);
up(k);
}
else
{
cin >> k >> x;
k = ph[k];
h[k] = x;
down(k);
up(k);
}
}
return 0;
}
4、哈希表
4.1 模板
(1) 拉链法
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
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;
}
(2) 开放寻址法
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
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;
}
4.2 例题
题目来源:AcWing
题目链接:AcWing 840. 模拟散列表
代码展示:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, null = 0x3f3f3f3f;
int n, 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, 0x3f, sizeof h);
cin >> n;
while (n -- )
{
string op;
int x;
cin >> op >> x;
if (op == "I")
{
int t = find(x);
h[t] = x;
}
else
{
int t = find(x);
if (h[t] == x) puts("Yes");
else puts("No");
}
}
return 0;
}
题目来源:AcWing
题目链接:AcWing 841. 字符串哈希
代码展示:
#include <iostream>
using namespace std;
typedef unsigned long long ULL;
const int N =1e5 + 10, P = 131;
int n, m;
int h[N], p[N];
char str[N];
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
cin >> n >> m;
scanf("%s", str + 1);
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
while (m -- )
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}