题目描述
这是一道模板题。
维护一个 n 点的无向图,支持:
- 加入一条连接 u 和 v 的无向边
- 查询 u 和 v 的连通性
由于本题数据较大,因此输出的时候采用特殊的输出方式:用 00 或 11 代表每个询问的答案,将每个询问的答案依次从左到右排列,把得到的串视为一个二进制数,输出这个二进制数 mod 998244353mod 998244353 的值。
请务必使用快读。
输入格式
第一行包含两个整数n,m,表示点的个数和操作的数目。
接下来 m 行每行包括三个整数 op,u,v。
- 如果 op=0op=0,则表示加入一条连接 u 和 v 的无向边;
- 如果 op=1op=1,则表示查询 u 和 v 的连通性。
输出格式
一行包括一个整数表示答案。
样例
Inputcopy | Outputcopy |
---|---|
3 6 1 1 0 0 0 1 1 0 1 1 1 2 0 2 1 1 2 1 | 5 |
答案串为 01010101。
数据范围与提示
n≤4000000,m≤8000000
#include <iostream> // 引入输入输出流库
using namespace std; // 使用标准命名空间
const int N = 4000010; // 定义一个常量 N,表示集合的最大大小
int fa[N]; // 创建一个数组 fa,用于存放每个元素的父节点信息
long long ans = 0ll; // 创建一个变量 ans 来存放最终的答案,使用 long long 类型以避免溢出
// 查找函数,用于找到元素 x 所在集合的代表元
int find(int x) {
if (fa[x] == x) { // 如果 x 是代表元,直接返回
return x;
} else
// 如果 x 不是代表元,递归查找 x 的父节点的代表元,并更新 fa[x]
return fa[x] = find(fa[x]);
}
int main() {
int n, m; // n 表示元素的数量,m 表示操作的数量
scanf("%d%d", &n, &m); // 从标准输入读取 n 和 m
for (int i = 0; i < n; i++) {
fa[i] = i; // 初始化时,每个元素都是自己的代表元
}
while (m--) { // 处理 m 个操作
int op, u, v; // op 表示操作类型,u 和 v 表示操作的元素
scanf("%d%d%d", &op, &u, &v); // 从标准输入读取操作信息
if (op == 0) { // 如果要合并两个集合
fa[find(u)] = find(v); // 将 u 的代表元的父节点设置为 v 的代表元
} else { // 如果要查询两个元素是否在同一个集合
ans <<= 1; // 将答案左移一位,相当于乘以 2,准备添加下一位查询结果
if (find(u) == find(v)) // 如果 u 和 v 在同一个集合中
ans += 1; // 添加 1 到答案中
ans %= 998244353; // 对答案取模,防止溢出
}
}
printf("%lld\n", ans); // 输出最终的答案
return 0; // 程序结束
}
并查集(Disjoint Set)是一种用于处理集合合并与查询的数据结构。它维护了一个集合的划分,每个集合通过一个代表元素来表示,可以高效地进行合并和查询两个元素是否在同一个集合中的操作。
代码中使用一个数组 fa
来存放每个元素的父节点信息,初始时,每个元素都是自己的代表元。通过 find
函数来查找元素所在集合的代表元,该函数使用了路径压缩的优化技巧,在查找过程中会将经过的节点的父节点直接更新为集合的代表元,以提高后续查询操作的效率。
在主函数中,首先从输入中读取元素的数量 n
和操作的数量 m
。然后使用一个循环来处理 m
个操作。每个操作由操作类型 op
和操作元素 u
、v
组成。如果操作类型为 0,表示要合并两个集合,则将以 u
和 v
为代表元的集合进行合并,即将 u
的父节点设置为 v
;如果操作类型为 1,表示要查询两个元素是否在同一个集合中,则根据 find
函数的返回值判断。
在查询操作中,为了方便计算答案,将答案 ans
左移一位,并根据查询结果判断是否将 1 添加到答案中。最后,输出答案。
需要注意的是,代码中对答案 ans
进行了取模操作,防止溢出。