基础数据结构
目录
-
并查集
-
单调队列
-
单调栈
-
堆
-
哈希表
并查集
维护集合(简单树)的一种数据结构。
优化复杂度需要路径压缩或按秩合并(秩指的是深度或大小),一起使用可以做到反阿克曼函数复杂度。
此外并查集可以通过记录额外信息,边带权,扩展域解决更多问题。
首先,一个典型的例子是每次输出元素所在集合的大小,那么就要额外记录集合的大小。
代码简单,唯一的区别是 merge 时的操作不同。
void merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return;
f[fx] = fy;
siz[fy] += siz[fx];
}
查询的时候使用 find(x) 作为代表元素即可。
然后先介绍扩展域,因为扩展域本质上是一种同余边带权。
【例题 1】P1892 [BOI2003] 团伙
这题很明显有两个“域”,朋友和敌人。抽象成一个并查集的问题,最多的团体就是森林中树的数量。
【例题 2】P2024 [NOI2001] 食物链
这是一题很经典的三域并查集,有吃,被吃,同类,三种可能,合理安排关系是可以做的,但是这题可以引出边带权的解法。
边带权,实际上就进一步维护了并查集的本质,一个森林。
下面给出食物链的参考代码:
#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define dF(i, a, b) for (int i = (a); i >= (b); i--)
using namespace std;
typedef long long ll;
typedef pair<int, int> Pair;
const int N = 100005, M = (N << 1), inf = 0x3f3f3f3f, mod = 1e9 + 7;
int n, k, ans, f[N], d[N];
int find(int x) {
if (f[x] == x) return f[x];
int res = find(f[x]);
(d[x] += d[f[x]]) %= 3;
return f[x] = res;
}
void merge(int x, int y, int z) {
int fx = find(x), fy = find(y);
if (fx == fy) return;
f[fx] = fy;
d[fx] = (z - d[x] + d[y] + 3) % 3;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> k;
F(i, 1, n) f[i] = i;
F(i, 1, k) {
int op, x, y;
cin >> op >> x >> y;
if (x > n || y > n || (op == 2 && x == y)) ans++;
else if (op == 1) {
if (find(x) == find(y) && (d[x] - d[y] + 3) % 3 != 0) ans++;
else merge(x, y, 0);
} else {
if (find(x) == find(y) && (d[x] - d[y] + 3) % 3 != 1) ans++;
else merge(x, y, 1);
}
}
cout << ans;
return 0;
}
在这里,两个函数都有很大改变,d表示一个点到达祖先的距离,两个点之间就可用距离之差来表达关系。
这里,有两个很重要的点。
find 里面要先递归才能累加,可画图理解。
merge 必须判断是否已经在同一集合。
习题:P9869 [NOIP2023] 三值逻辑。
单调队列
\(\mathcal{O}(n)\) 解决定长区间 RMQ。
超时的删除,符合要求的加入,维护队列始终单调即可。
单调栈
和单调队列的区别是没有超时元素。
常用于求解每个数左边或右边第一个比其大(小)的数字。
习题:[ABC311G] One More Grid Task。
堆
普通的直接用优先队列即可。
需要合并的话考虑左偏树。
记录一个点的 \(\mathrm{dist}\) 是这个点到其子树内叶子节点的最近距离。
左偏树是一棵二叉树,它不仅具有堆的性质,并且是「左偏」的:每个节点左儿子的 \(\mathrm{dist}\) 都大于等于右儿子的 \(\mathrm{dist}\)。
注意:左偏树的深度没有保证。
合并的话类似线段树合并,每次取一个最小的点作为根,递归去做,最后判断一下 \(\mathrm{dist}\) 并交换左右儿子,当然有一种随机交换的方法,复杂度也是对的。
哈希表
首先设置一个大质数 \(P \le 10 ^ 7\),对于一个值,将其模 \(P\) 后的值记为 key,若 key 冲突那么向后跳直到不冲突为止,记录 val 即可。

浙公网安备 33010602011771号