洛谷 题解 P5877 【棋盘游戏】

一道很好的二维并查集模板题。

题目大意很简单:在一个\(N×N\)的棋盘上放\(M\)个棋子,求每放一次后棋盘上的连通块的个数

如果考虑\(DFS\)\(BFS\)来求连通块的话,显然会\(T\)

那么,我们该用什么高效的算法或数据结构来解决这个问题呢?

在讲普通的一维并查集的时候,我们采用了一种代表元的思想:一个集合用一个元素作为它的代表,将集合内的所有元素的父节点指向这个代表元,最后根据每个元素的父节点来判断元素所在集合。

那么,这个思想在求连通块个数时有什么应用呢?

我们可以认定一个棋子为代表元,在想四个方向扩展,如果有相同的棋子,就可以将这颗棋子的父节点指向原先的节点,这样不停的拓展下去,会构成一个集合(也就是这个连通块),而代表元就是原先的那枚棋子。

这样,集合个数即为我们所求。

那么,下面我来介绍一下二维并查集的实现。

首先,考虑\(f\)数组的定义。由于棋盘是二维的,我们可以有二维数组;同时,每个代表元也是一个二维的坐标,所以,我们可以将\(f\)数组定义为一个结构体,里面存着\((i,j)\)的代表元的坐标\((x,y)\)

代码如下:

struct node {
    int x, y;
};
node f[MAXN][MAXN];

仿照一维并查集,我们可以对二维并查集有如下操作:

\(·init()\):初始化集合,即将每个位置的代表元设为自己

\(·find(x)\):求\(x\)所在的集合的代表元

\(·unite(x, y)\):将\(x\)\(y\)所在的集合合并

\(·same(x, y)\):询问\(x\)\(y\)是否在一个集合内。

下面我来一一讲解。

对于\(init\)操作,十分简单:\(f_{i,j}=(node\{i, j\})\)

void init() {
    _for (i, 1, n)
        _for (j, 1, n) 
            f[i][j] = (node){i, j};
}

对于\(find\)操作,这就需要一定的思维。

这个\(find\)操作,我们也要进行路径压缩。

当查询到代表元就是自己时,判断标准自然是\(f_{i,j}\)的坐标和自己的坐标相同,扩展开写就是\(f_{i,j}.x == d.x\) \(&&\) \(f_{i,j}.y==d.y\),此时就可以return

否则的话,就要更新当前节点的代表元。

node find(node d) {
    if (f[d.x][d.y].x == d.x && f[d.x][d.y].y == d.y) return d;
    return f[d.x][d.y] = find(f[d.x][d.y]);
}

对于\(unite\)操作,则是十分简单。

void unite(node d1, node d2) {
    d1 = find(d1);
    d2 = find(d2);
    if (d1.x == d2.x && d1.y == d2.y) return ;
    f[d1.x][d1.y] = d2;  
}

\(same\)操作就只是判断两个元素的代表元坐标是否相等即可。

bool same(node d1, node d2) {
    d1 = find(d1);
    d2 = find(d2);
    return d1.x == d2.x && d1.y == d2.y;
}

大体上就是差不多。其实我们发现,我在上面大量用到了判断坐标相等,所以我们可以考虑对于\(node\)来重载\(==\)这个运算符,这留给读者自己思考。

然后要根据每一次部署棋子来更新集合个数。我们可以从每一次部署的位置向四周扩展,如果遇到同类棋子,可以判断是否处在同一集合里,不是就可以合并。

至于统计答案,我们可以先设定\(ans=0\),每下一个棋子\(ans++\),但一旦发现周围有同类棋子且集合不一样时,合并集合的同时,\(ans--\),最后每次输出\(ans\)即可。

再讲一下一个小坑点。如果你一开始的棋盘数组初始化是\(0\),输入\(c\)时不能等于\(0\),否则。。。\(GG\)

所以你可以对输入的\(c\)进行变换(例如\(+1\)什么的)

差不多了,上代码!

\(AC\) \(Code\)

#include 
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
using namespace std;

const int MAXN = 510, dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};//方向数组

struct node {
    int x, y;
};
node f[MAXN][MAXN];

int n, m, map[MAXN][MAXN], ans;

void init() {
    _for (i, 1, n)
        _for (j, 1, n) 
            f[i][j] = (node){i, j};
}

node find(node d) {
    if (f[d.x][d.y].x == d.x && f[d.x][d.y].y == d.y) return d;
    return f[d.x][d.y] = find(f[d.x][d.y]);
}

void unite(node d1, node d2) {
    d1 = find(d1);
    d2 = find(d2);
    if (d1.x == d2.x && d1.y == d2.y) return ;
    f[d1.x][d1.y] = d2;  
}

bool same(node d1, node d2) {
    d1 = find(d1);
    d2 = find(d2);
    return d1.x == d2.x && d1.y == d2.y;
}//二维并查集基本操作

bool cango(int x, int y) {
    return x > 0 && x <= n && y > 0 && y <= n;
}//判断是否出界

int main() {
    cin >> n >> m;
    init();//初始化莫忘掉
    while (m--) {
        int c, a, b;
        cin >> c >> a >> b;
        node now;
        now.x = a, now.y = b;
        map[a][b] = c+1;//变换莫忘掉
        ans++;
        _for (i, 0, 3) {
            int nx = a + dir[i][0], ny = b + dir[i][1]; 
            node next;
            next.x = nx, next.y = ny;
            if (cango(nx, ny) && map[a][b] == map[nx][ny]) {//一定要判断棋子种类是否一样
                if (!same(now, next)) {
                    unite(now, next);
                    ans--;
                }
            }
        }
        cout << ans << endl;//换行莫忘掉
    }
    return 0;//完结撒花!
}

你可能感兴趣的:(洛谷 题解 P5877 【棋盘游戏】)