题解:洛谷 P10379 俄罗斯方块

【题目来源】

洛谷:P10379 [GESP202403 七级] 俄罗斯方块 - 洛谷

【题目描述】

小杨同学用不同种类的俄罗斯方块填满了一个大小为 \(n \times m\) 的网格图。

网格图由 \(n \times m\) 个带颜色方块构成。小杨同学现在将这个网格图交给了你,请你计算出网格图中俄罗斯方块的种类数。
如果两个同色方块是四连通(即上下左右四个相邻的位置)的,则称两个同色方块直接连通;若两个同色方块同时与另一个同色方块直接或间接连通,则称两个同色方块间接连通。一个俄罗斯方块由一个方块和所有与其直接或间接连接的同色方块组成。定义两个俄罗斯方块的种类相同当且仅当通过平移其中一个俄罗斯方块可以和另一个俄罗斯方块重合;如果两个俄罗斯方块颜色不同,仍然视为同一种俄罗斯方块。

例如,在如下情况中,方块 \(1\) 和方块 \(2\) 是同一种俄罗斯方块,而方块 \(1\) 和方块 \(3\) 不是同一种俄罗斯方块。

【输入】

第一行包含两个正整数 \(n\)\(m\),表示网格图的大小。
对于之后的 \(n\) 行,第 \(i\) 行包含 \(m\) 个正整数 \(a_{i1}, a_{i2}, \dots a_{im}\),表示该行 \(m\) 个方块的颜色。

【输出】

输出一行一个整数表示答案。

【输入样例】

5 6
1 2 3 4 4 5
1 2 3 3 4 5
1 2 2 3 4 5
1 6 6 7 7 8
6 6 7 7 8 8

【输出样例】

7

【核心思想】

  1. 问题分析:给定 \(n \times m\) 的网格,每个格子有颜色。同色四连通(上下左右)的方块组成一个俄罗斯方块。两个俄罗斯方块种类相同当且仅当可以通过平移重合(颜色不同也可视为同一种)。求不同种类的俄罗斯方块数量。这是一个连通块提取 + 形状标准化 + 集合去重问题,核心在于将每个连通块转化为与位置无关的标准形状表示,然后去重统计。

  2. 算法选择

    • DFS/BFS 提取连通块:遍历网格,对每个未访问的同色区域进行四连通搜索
    • 相对坐标标准化:以连通块的左上角为原点,记录所有格子的相对坐标
    • 集合去重:将标准化后的相对坐标向量存入 set 自动去重
  3. 关键步骤

    • 读入数据:读取 \(n, m\) 和网格 \(a[1..n][1..m]\)
    • 遍历网格\(i\)\(1\)\(n\)\(j\)\(1\)\(m\)):
      • \(vis[i][j] = 0\):发现新连通块
        • \(sx \leftarrow i, sy \leftarrow j\)(记录起始点)
        • \(dfs(i, j)\):搜索整个连通块,将 \((x-sx, y-sy)\) 存入向量 \(e\)
        • \(se.insert(e)\):将相对坐标向量插入集合
        • \(e.clear()\)
    • 输出结果\(se.size()\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \cdot m \cdot \log(n \cdot m))\),DFS 遍历网格,集合插入为 \(O(k \log k)\)\(k\) 为连通块大小)
    • 空间复杂度:\(O(n \cdot m)\),网格、访问标记和集合
  5. 形状标准化与集合去重的核心思想

    • 平移不变性:通过相对坐标 \((x-sx, y-sy)\) 消除绝对位置影响,使得不同位置但形状相同的连通块具有相同的表示
    • 四连通定义:只考虑上下左右相邻,不考虑对角线,符合俄罗斯方块的经典连通定义
    • 集合自动去重set<vector<pair<int,int>>> 利用向量的字典序比较自动去重,无需手动判断形状是否相同
    • 颜色无关性:题目明确"颜色不同仍视为同一种",因此只需关注连通块的形状(相对坐标),无需记录颜色信息
    • 起始点选择:以 DFS 的起始点 \((sx, sy)\) 为原点,确保同一连通块内所有点的相对坐标非负且最小化
    • 适用于连通块形状识别、平移不变性去重类问题

【算法标签】

普及 #DFS-二维

【代码详解】

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

const int N = 505;
int n, m, k, s, t, sx, sy;    // n,m: 网格大小, sx,sy: 当前连通块的起始点
int a[N][N];                  // 存储网格中的值
int ans;                      // 结果,不同形状的数量
int vis[N][N];                // 访问标记数组

// 四个方向的偏移量:右、下、左、上
int d[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

vector<pair<int, int>> e;  // 存储当前连通块中所有点的相对坐标
set<vector<pair<int, int>>> se;  // 存储所有不同形状的连通块

// 深度优先搜索,用于寻找连通块
void dfs(int x, int y)
{
    // 将当前点的相对坐标(相对于连通块起始点)存入向量e
    e.push_back({x - sx, y - sy});

    // 标记当前点已访问
    vis[x][y] = 1;

    // 遍历四个方向
    for (int i = 0; i < 4; i++)
    {
        int xx = x + d[i][0];  // 新的x坐标
        int yy = y + d[i][1];  // 新的y坐标

        // 检查新坐标是否有效
        if (xx >= 1 && xx <= n && yy >= 1 && yy <= m &&  // 在网格范围内
            a[xx][yy] == a[x][y] &&                      // 值相同
            !vis[xx][yy])                                 // 未访问过
        {
            dfs(xx, yy);  // 继续深度优先搜索
        }
    }
}

int main()
{
    // 输入网格大小
    cin >> n >> m;

    // 输入网格数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
        }
    }

    // 遍历整个网格
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            // 如果当前点未访问过
            if (!vis[i][j])
            {
                // 记录连通块的起始点
                sx = i;
                sy = j;

                // 从当前点开始深度优先搜索,找到整个连通块
                dfs(i, j);

                // 将当前连通块的形状(相对坐标向量)插入集合
                // 集合会自动去重
                se.insert(e);

                // 清空向量,为下一个连通块做准备
                e.clear();
            }
        }
    }

    // 输出不同形状的数量
    cout << se.size() << endl;

    return 0;
}

【运行结果】

5 6
1 2 3 4 4 5
1 2 3 3 4 5
1 2 2 3 4 5
1 6 6 7 7 8
6 6 7 7 8 8
7
posted @ 2026-07-17 15:53  团爸讲算法  阅读(7)  评论(0)    收藏  举报