[GESP202403 七级] 俄罗斯方块

前言:

起因是有人说我出的都是水题题解,真的很水嘛?还是我太菜了

那么这次出一期七级题解


正文:

题目大意

有一个 \(n \times m\) 的彩色网格,每个格子有一种颜色。
相邻(上下左右)且颜色相同的格子连在一起,会形成一个“俄罗斯方块”。
如果两个俄罗斯方块可以通过平移完全重合(即使颜色不同也算同一种),我们就认为它们是同一种形状。
现在需要统计整个网格里不同形状的俄罗斯方块有多少种。

问题拆分

问题可以分成两部分:

  1. 把网格中所有的连通块找出来。
  2. 判断哪些连通块的形状相同。

1.基本思路

找连通块一般会用 BFS 或 DFS,但这里我们使用并查集。
思路是:如果一个格子和它右边或下边的邻居颜色相同,就把它们合并到同一个集合里。这样每个集合的根节点就代表了一个连通块。

好处是代码中不需要队列或递归,简单。

2. 判断形状是否相同

平移不变的意思是:无论方块在网格的哪个位置,只要内部的相对位置关系完全一样,就是同一种形状。

所以我们使用如下思路:

  • 对每个连通块,找出它所有格子中最小的行号和最小的列号,作为“基点(我不会起名,见谅)”。
  • 把连通块中所有格子的坐标都减去这个基点,得到一个数(不起了)。
  • 把这些数先按行、再按列”的顺序排序,确保相同的形状总能排成一样的顺序。
  • 把排序后的这堆数拼成一个字符串(比如 "0,0;0,1;1,0;"),作为这个形状的“标准形式”。

最后,把所有指纹放进一个 vector里,因为 vector 会自动去重,最后 vector 的大小就是不同形状的数量。

3. 遍历(暴力)

把所有合法的二郎神方块全部遍历一遍
其实可以用map,但这里可以用一个更简单的办法 绝对不是因为我懒得写map:

  • 用一个数组 记录每个根节点对应的连通块编号(初始 -1,记得做初始化,不写真的会炸)。
  • 遍历所有格子,如果发现新的基点,就给它分配一个新编号,并在一个 vector 里新开一个列表。
  • 把格子的坐标直接加入对应的列表中。

流程总结(不在代码里写注释了)

  1. 输入网格大小 \(n,m\) 和颜色矩阵。
  2. 初始化并查集,把每个格子看成一个独立的集合。
  3. 遍历每个格子
  4. 再遍历一遍所有格子,根据基点,将坐标归到不同的列表中。
  5. 输出大小。

复杂度(ai分析,不一定准)

合并和收集坐标的步骤都非常快,主要时间花在对每个连通块的坐标排序上。
最坏情况整个网格是一个连通块,排序需要 \(O(nm \log(nm))\) 时间,对于 \(500\times500\) 的数据完全可以接受。

注释:resize()为

参考代码

#include <bits/stdc++.h>
using namespace std;

int f[250005];
int F(int x) { return f[x] == x ? x : f[x] = F(f[x]); }
void U(int x, int y) { f[F(x)] = F(y); }

int main() {
    int n, m; cin >> n >> m;
    vector<vector<int>> a(n, vector<int>(m));
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j) cin >> a[i][j];

    for (int i = 0; i < n * m; ++i) f[i] = i;

    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j) {
            int p = i * m + j;
            if (j + 1 < m && a[i][j] == a[i][j + 1]) U(p, p + 1);
            if (i + 1 < n && a[i][j] == a[i + 1][j]) U(p, p + m);
        }

    vector<int> r(n * m, -1);
    vector<vector<pair<int, int>>> b;

    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j) {
            int t = F(i * m + j);
            if (r[t] == -1) {
                r[t] = b.size();
                b.push_back({});
            }
            b[r[t]].push_back({i, j});
        }

    set<string> s;
    for (int k = 0; k < b.size(); ++k) {
        int R = n, C = m;
        for (int i = 0; i < b[k].size(); ++i) {
            R = min(R, b[k][i].first);
            C = min(C, b[k][i].second);
        }
        vector<pair<int, int>> v;
        for (int i = 0; i < b[k].size(); ++i)
            v.push_back({b[k][i].first - R, b[k][i].second - C});
        sort(v.begin(), v.end());
        string z;
        for (int i = 0; i < v.size(); ++i)
            z += to_string(v[i].first) + "," + to_string(v[i].second) + ";";
        s.insert(z);
    }
    cout << s.size() << endl;
}
posted @ 2026-10-01 18:48  Spencer20262026  阅读(5)  评论(0)    收藏  举报