洛谷P5877 棋盘游戏
前言
蒟蒻来水题解啦
Description
给定一个 \(N * N\) 的矩阵中的 \(M\) 个点,求这些点中的连通块数量。
Solution
正常人看到这道题的第一反应应该是广搜吧。但是,很明显使用 BFS 会超时:我们需要求 \(M\) 次连通块的数量。
那么怎么办呢?凉拌蒟蒻! 我们可以使用 二维并查集 进行统计。
为什么我们可以使用并查集呢?我们可以把一个连通块想象成一个树,新加进来(或与其相邻的)的节点都是它的子节点。那么,这棵树的根就是这个连通块在并查集里的祖先。
那还剩下一个问题。二维并查集这么搞?
其实我们不需要真正创建一个二维的并查集。我们可以把这个矩阵进行压缩,使其成为一个我们喜闻乐见熟悉的一维并查集。
那么,对于一个点 \((x, y)\) ,它在并查集数组中的下标应该是 \(x \times N + y\) 。
然后,对于每个新添加的节点,我们都会检测它上、下、左、右四个方位有没有相同颜色的节点。如果有,那么继续判断该节点是否跟当前节点为同一个祖先。如果不是,那么我们将它们合并,使其祖先同一。
Code
下面,让大家来看看这个蒟蒻难看的代码啦~
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 510, M = N * N; // M是最多的矩阵的点数
int n, m, father[M], a[N][N], ans; // father数组存储每个节点祖先,a数组用于存储每个节点的颜色,ans变量为当前连通块的数量
int dx[] = {0, 0 ,1, -1}, dy[] = {1, -1, 0, 0}; // 类似BFS,上下左右四个点的偏移量
int Find(int x) { // 查询祖先
if (father[x] == x) return x; // 如果祖先是自己,返回
return father[x] = Find(father[x]); // 否则递归查找
}
void Union(int u, int v) { // 合并节点
int fu = Find(u), fv = Find(v); // 获取祖先
if (fu != fv) father[fu] = fv, ans--; // 如果祖先不同,那么合并,并且连通块数量 - 1
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i < M; i++) father[i] = i; // 初始化并查集,每个节点的祖先都是自己(即默认有M个连通块)
for (int i = 1; i <= m; i++) {
int c, x, y;
scanf("%d%d%d", &c, &x, &y);
a[x][y] = c + 1, ans++; // c + 1是因为C++默认数组初始是0,与默认值区分开
for (int i = 0; i < 4; i++) { // 循环4个方位
int nx = x + dx[i], ny = y + dy[i]; // 当前新的节点坐标
if (a[nx][ny] != a[x][y]) continue; // 如果不是同一个颜色那就没必要合并了
Union(nx * n + ny, x * n + y); // 合并节点,判断祖先的操作在Union函数里
}
printf("%d\n", ans); // 输出当前连通块数量
}
return 0; // 完美结束!
}

浙公网安备 33010602011771号