1 条题解

  • 0
    @ 2025-11-11 8:26:29

    题意理解 我们有一个 N × M N×M 的网格,每个格子有一个目标颜色(大写字母)。

    Bessie 一笔可以涂一个连通分量(四连通),要求这一笔涂的所有格子颜色相同。

    给定 Q Q 个矩形区域,对每个矩形,问最少需要多少笔才能把矩形内部所有格子涂成目标颜色(矩形外部不涂)。

    N , M ≤ 1000 N,M≤1000

    Q ≤ 1000 Q≤1000

    初步分析 对于单个矩形,我们可以把它看作一个子网格图,每个格子是一个顶点,相邻且颜色相同的格子之间连一条边。

    那么最少笔数 = 该子矩形中颜色相同连通分量的数量。

    但是注意:即使两个格子颜色相同,如果它们在子矩形中不连通(因为路径必须完全在子矩形内),那么它们需要分开涂。

    所以问题转化为: 对于矩形区域,计算颜色相同且四连通的连通块个数。

    直接做法与复杂度 对每个查询,在矩形内做 BFS/DFS 找连通块,复杂度 O ( Q ⋅ N M ) O(Q⋅NM),太大( 10 9 10 9 级别)。

    需要预处理。

    平面图与欧拉公式 这是一个平面图问题: 我们把每个格子看作一个面(face),相邻且同色的格子属于同一个面。

    更准确地说: 我们构造一个图 G G:

    顶点 = 每个格子

    边 = 相邻且同色的格子之间的边

    那么每个连通分量是一个面(在子矩形中可能被切断)。

    欧拉公式 对于一个平面连通图:

    V − E + F

    1 + C V−E+F=1+C 其中:

    V V = 顶点数

    E E = 边数

    F F = 面数(包括外面)

    C C = 连通分量数

    但我们这里不是对整个图,而是对子矩形。

    关键思路 对于子矩形 R R,定义:

    V V = 矩形内格子数

    E E = 矩形内相邻且同色的边数(边两端都在矩形内)

    C C = 矩形内连通块数(我们要求的答案)

    但是直接套欧拉公式不行,因为子矩形不是整个平面图。

    对偶图方法 考虑构造对偶图:

    原图的顶点 = 格子中心

    原图的边 = 相邻格子之间的边(如果同色则边存在,否则不存在)

    我们要求的是原图在子矩形中的连通分量数。

    利用边界和环 一个已知结论(类似 USACO 官方题解): 对于子矩形,定义:

    V V = 格子数

    E E = 内部同色边数

    B B = 与矩形外部相邻的边数(即矩形边界上的边,且该边连接的两个格子颜色不同)

    H H = 矩形内部“洞”的数量(即被矩形包围但颜色不同的区域形成的环)

    那么:

    C

    V − E + H + B 2 ? C=V−E+H+ 2 B ​ ? 实际上更准确的是:

    官方解法思路 定义:

    对每个格子,它是一个面。

    相邻且同色的格子属于同一个面。

    在子矩形中,一个“笔划”对应一个同色连通分量。

    我们构造对偶图:

    顶点 = 每个格子中心

    边 = 相邻格子之间的边(如果同色则边存在,否则不存在)

    那么子矩形中的连通分量数 = 顶点数 - 边数 + 环数 + 边界调整项。

    更精确公式(经过推导):

    笔数

    ( 顶点数 ) − ( 内部边数 ) + ( 边界上的连通块数 ) 笔数=(顶点数)−(内部边数)+(边界上的连通块数) 其中“边界上的连通块数”指在矩形边界上,相邻且同色的格子形成的连通块数。

    高效计算 我们可以预处理:

    V[x1,y1,x2,y2] = 矩形内格子数 = (x2-x1+1)*(y2-y1+1)

    E[x1,y1,x2,y2] = 矩形内相邻且同色的边数(水平+垂直)

    B[x1,y1,x2,y2] = 矩形边界上同色连通块数

    那么:

    答案

    V − E + B 答案=V−E+B 预处理 E E 可以分成水平边和垂直边:

    水平边:对每行 i,列 j 从 1 到 M-1,如果 grid[i][j] == grid[i][j+1],则存在一条水平边 (i,j)-(i,j+1)。

    我们可以用二维前缀和 hor[i][j] 表示从 (1,1) 到 (i,j) 的水平边数。

    类似地,垂直边:对每列 j,行 i 从 1 到 N-1,如果 grid[i][j] == grid[i+1][j],则存在一条垂直边 (i,j)-(i+1,j)。

    用二维前缀和 ver[i][j] 表示从 (1,1) 到 (i,j) 的垂直边数。

    那么矩形 (x1,y1,x2,y2) 内的边数 =

    水平边:hor[x2][y2-1] - hor[x1-1][y2-1] - hor[x2][y1-1] + hor[x1-1][y1-1](注意 y2-1 因为水平边在 j 和 j+1 之间)

    垂直边:ver[x2-1][y2] - ver[x1-1][y2] - ver[x2-1][y1-1] + ver[x1-1][y1-1](注意 x2-1 因为垂直边在 i 和 i+1 之间)

    预处理 B 边界上的连通块数 = 上边界 + 下边界 + 左边界 + 右边界 - 四个角重复计算。

    我们可以对每个边界单独计算连通块数,然后减去角上重复的。

    具体:

    上边界:行 x1,列 y1..y2

    下边界:行 x2,列 y1..y2

    左边界:列 y1,行 x1..x2

    右边界:列 y2,行 x1..x2

    对每个边界,数一下颜色变化的次数(相邻格子颜色不同则断开),连通块数 = 变化次数 + 1。

    然后减去四个角,因为角在两条边界都被算了一次。

    算法步骤 读入 N, M, Q 和网格

    预处理水平边前缀和 hor 和垂直边前缀和 ver

    对每个查询 (x1,y1,x2,y2):

    V = (x2-x1+1)*(y2-y1+1)

    E = 水平边数 + 垂直边数(用前缀和计算)

    B = 边界连通块数(单独计算四条边,减角)

    答案 = V - E + B

    边界连通块数计算 以上边界为例:

    cpp int top = 1; for (int j = y1+1; j <= y2; j++) { if (grid[x1][j] != grid[x1][j-1]) top++; } 类似计算 bottom, left, right。

    然后:

    cpp B = top + bottom + left + right; // 减去四个角重复 if (grid[x1][y1] != grid[x1][y1+1] && grid[x1][y1] != grid[x1+1][y1]) B--; // 其他三个角类似 实际上更简单:每个角如果与相邻两边颜色都不同,则它在两条边界上都被算作新块,多算了一次,所以要减 1。

    复杂度 预处理 O(NM)

    每个查询 O(N+M)(因为要遍历边界)

    总 O(NM + Q(N+M)),可接受(约 2×10^6)

    代码框架 cpp #include <bits/stdc++.h> using namespace std;

    int N, M, Q; vector grid; vector<vector> hor, ver;

    int main() { cin >> N >> M >> Q; grid.resize(N+1); for (int i = 1; i <= N; i++) { string s; cin >> s; grid[i] = " " + s; }

    // 预处理水平边前缀和
    hor.assign(N+1, vector<int>(M+1, 0));
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j < M; j++) {
            hor[i][j] = hor[i-1][j] + hor[i][j-1] - hor[i-1][j-1];
            if (grid[i][j] == grid[i][j+1]) hor[i][j]++;
        }
        hor[i][M] = hor[i][M-1]; // 扩展最后一列
    }
    
    // 预处理垂直边前缀和
    ver.assign(N+1, vector<int>(M+1, 0));
    for (int j = 1; j <= M; j++) {
        for (int i = 1; i < N; i++) {
            ver[i][j] = ver[i-1][j] + ver[i][j-1] - ver[i-1][j-1];
            if (grid[i][j] == grid[i+1][j]) ver[i][j]++;
        }
        ver[N][j] = ver[N-1][j]; // 扩展最后一行
    }
    
    while (Q--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        
        int V = (x2 - x1 + 1) * (y2 - y1 + 1);
        
        // 计算 E
        int E = 0;
        // 水平边
        if (y2 > y1) {
            E += hor[x2][y2-1] - hor[x1-1][y2-1] - hor[x2][y1-1] + hor[x1-1][y1-1];
        }
        // 垂直边
        if (x2 > x1) {
            E += ver[x2-1][y2] - ver[x1-1][y2] - ver[x2-1][y1-1] + ver[x1-1][y1-1];
        }
        
        // 计算边界连通块数 B
        int B = 0;
        // 上边界
        int top = 1;
        for (int j = y1+1; j <= y2; j++) {
            if (grid[x1][j] != grid[x1][j-1]) top++;
        }
        // 下边界
        int bottom = 1;
        for (int j = y1+1; j <= y2; j++) {
            if (grid[x2][j] != grid[x2][j-1]) bottom++;
        }
        // 左边界
        int left = 1;
        for (int i = x1+1; i <= x2; i++) {
            if (grid[i][y1] != grid[i-1][y1]) left++;
        }
        // 右边界
        int right = 1;
        for (int i = x1+1; i <= x2; i++) {
            if (grid[i][y2] != grid[i-1][y2]) right++;
        }
        
        B = top + bottom + left + right;
        
        // 减去四个角重复
        // 左上角
        if (x1 > 0 && y1 > 0 && grid[x1][y1] != grid[x1][y1+1] && grid[x1][y1] != grid[x1+1][y1]) B--;
        // 右上角
        if (x1 > 0 && y2 < M && grid[x1][y2] != grid[x1][y2-1] && grid[x1][y2] != grid[x1+1][y2]) B--;
        // 左下角
        if (x2 < N && y1 > 0 && grid[x2][y1] != grid[x2][y1+1] && grid[x2][y1] != grid[x2-1][y1]) B--;
        // 右下角
        if (x2 < N && y2 < M && grid[x2][y2] != grid[x2][y2-1] && grid[x2][y2] != grid[x2-1][y2]) B--;
        
        int ans = V - E + B;
        cout << ans << "\n";
    }
    
    return 0;
    

    } 总结 这道题的核心是:

    将最少笔数问题转化为子矩形内同色连通块计数

    利用平面图性质得到公式:笔数 = 顶点数 - 边数 + 边界连通块数

    用二维前缀和预处理边数

    对每个查询扫描边界计算边界连通块数

    这样就能在 O ( N M + Q ( N + M ) ) O(NM+Q(N+M)) 时间内解决。

    • 1

    信息

    ID
    4908
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者