题解:洛谷 AT_abc464_b Crop
【题目来源】
洛谷:AT_abc464_b [ABC464B] Crop - 洛谷
【题目描述】
有一张高为 \(H\) 像素、宽为 \(W\) 像素的黑白图片。第 \(i\) 行第 \(j\) 列像素的颜色用字符 \(C_{i,j}\) 表示,其中 . 代表白色,# 代表黑色。
请按照如下顺序,从图像的上下左右四边,依次去除全为白色的行和列:
- 当图片顶端的最上面一行全部为白色时,反复删去最上面一行。
- 当图片底端的最下面一行全部为白色时,反复删去最下面一行。
- 当图片最左端的最左边一列全部为白色时,反复删去最左边一列。
- 当图片最右端的最右边一列全部为白色时,反复删去最右边一列。
输出处理后的图片。
保证输入的图片中至少存在一个黑色像素。
【输入】
输入从标准输入读取,格式如下:
\(H\) \(W\)
\(C_{1,1}C_{1,2}\ldots C_{1,W}\)
\(C_{2,1}C_{2,2}\ldots C_{2,W}\)
\(\vdots\)
\(C_{H,1}C_{H,2}\ldots C_{H,W}\)
【输出】
输出处理后的图片,格式如下。
此时 \(h\) 和 \(w\) 分别表示处理后图片的高和宽(像素),\(c_{i,j}\) 表示处理后第 \(i\) 行第 \(j\) 列像素的颜色,若为白色输入 .,若为黑色输入 #。
\(c_{1,1}c_{1,2}\ldots c_{1,w}\)
\(c_{2,1}c_{2,2}\ldots c_{2,w}\)
\(\vdots\)
\(c_{h,1}c_{h,2}\ldots c_{h,w}\)
【输入样例】
4 5
.....
..#..
.###.
.....
【输出样例】
.#.
###
【核心思想】
-
问题分析:给定一个 \(H \times W\) 的黑白图像(
.表示白色,#表示黑色),需要按顺序从四个边缘(上、下、左、右)移除所有全白的行或列,直到每个边缘遇到第一个非全白的行/列为止。由于保证至少有一个黑色像素,最终图像非空。本质上是在二维矩阵中定位包含黑色像素的最小行区间和列区间。 -
算法选择:
- 直接模拟:按照题目要求的顺序,逐边检查并标记全白行/列
- 边界收缩:通过四个方向的扫描确定保留区域的上边界 \(top\)、下边界 \(bottom\)、左边界 \(left\)、右边界 \(right\)
-
关键步骤:
- 读取图像:读入 \(H\)、\(W\) 和像素矩阵 \(C[1..H][1..W]\)
- 确定上边界 \(top\):从第 \(1\) 行开始向下扫描,找到第一个不全白的行
- 确定下边界 \(bottom\):从第 \(H\) 行开始向上扫描,找到第一个不全白的行
- 确定左边界 \(left\):从第 \(1\) 列开始向右扫描,找到第一个不全白的列
- 确定右边界 \(right\):从第 \(W\) 列开始向左扫描,找到第一个不全白的列
- 输出保留区域:输出 \(C[i][j]\),其中 \(i \in [top, bottom]\),\(j \in [left, right]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(H \times W)\),每个像素最多被检查常数次(行扫描和列扫描)
- 空间复杂度:\(O(H \times W)\),存储原始图像矩阵
-
模拟与边界收缩的核心思想:
- 方向独立性:四个边缘的移除操作互不影响最终结果(因为全白行/列的移除不会使内部的非全白行/列变成全白),因此可以直接计算四个边界而不必真的逐行逐列删除
- 最小包围矩形:问题等价于找到包含所有黑色像素的最小轴对齐矩形,四个边界分别由最上、最下、最左、最右的黑色像素位置决定
- 顺序无关性优化:虽然题目规定了上→下→左→右的顺序,但由于黑色像素的位置固定,实际边界结果与处理顺序无关,可独立计算四个边界后统一输出
- 适用于二维矩阵的边缘裁剪、最小包围区域定位等基础图像处理问题
【算法标签】
入门 #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 55; // 最大图像尺寸
int h, w; // h: 图像高度, w: 图像宽度
char a[N][N]; // a[i][j]: 原始图像第i行第j列的像素
int b[N][N]; // b[i][j]: 标记该像素是否被移除(1表示移除,0表示保留)
int main()
{
cin >> h >> w; // 读入图像高度和宽度
// 读入图像
for (int i = 1; i <= h; i++)
for (int j = 1; j <= w; j++)
cin >> a[i][j];
// ========== 第一步:移除顶部全白行 ==========
bool ok = true;
while (ok)
{
// 从上往下检查每一行
for (int i = 1; i <= h; i++)
{
bool flag = 1; // flag=1表示当前行全白
// 检查第i行是否全白
for (int j = 1; j <= w; j++)
{
if (a[i][j] == '#') // 发现黑色像素
{
flag = 0; // 不是全白行
break;
}
}
if (flag) // 第i行全白,标记移除
{
for (int j = 1; j <= w; j++)
b[i][j] = 1; // 标记该行为移除
}
else // 第i行不全白,停止检查(只移除连续顶部的全白行)
{
ok = false;
break;
}
}
}
// ========== 第二步:移除左侧全白列 ==========
ok = true;
while (ok)
{
// 从左往右检查每一列
for (int j = 1; j <= w; j++)
{
bool flag = 1; // flag=1表示当前列全白
// 检查第j列是否全白
for (int i = 1; i <= h; i++)
{
if (a[i][j] == '#') // 发现黑色像素
{
flag = 0; // 不是全白列
break;
}
}
if (flag) // 第j列全白,标记移除
{
for (int i = 1; i <= h; i++)
b[i][j] = 1; // 标记该列为移除
}
else // 第j列不全白,停止检查
{
ok = false;
break;
}
}
}
// ========== 第三步:移除底部全白行 ==========
ok = true;
while (ok)
{
// 从下往上检查每一行
for (int i = h; i >= 1; i--)
{
bool flag = 1; // flag=1表示当前行全白
// 检查第i行是否全白
for (int j = 1; j <= w; j++)
{
if (a[i][j] == '#') // 发现黑色像素
{
flag = 0; // 不是全白行
break;
}
}
if (flag) // 第i行全白,标记移除
{
for (int j = 1; j <= w; j++)
b[i][j] = 1; // 标记该行为移除
}
else // 第i行不全白,停止检查
{
ok = false;
break;
}
}
}
// ========== 第四步:移除右侧全白列 ==========
ok = true;
while (ok)
{
// 从右往左检查每一列
for (int j = w; j >= 1; j--)
{
bool flag = 1; // flag=1表示当前列全白
// 检查第j列是否全白
for (int i = 1; i <= h; i++)
{
if (a[i][j] == '#') // 发现黑色像素
{
flag = 0; // 不是全白列
break;
}
}
if (flag) // 第j列全白,标记移除
{
for (int i = 1; i <= h; i++)
b[i][j] = 1; // 标记该列为移除
}
else // 第j列不全白,停止检查
{
ok = false;
break;
}
}
}
// ========== 输出处理后的图像 ==========
for (int i = 1; i <= h; i++)
{
for (int j = 1; j <= w; j++)
{
if (!b[i][j]) // 只输出未被移除的像素
cout << a[i][j];
}
cout << endl; // 每行结束换行
}
return 0;
}
【运行结果】
4 5
.....
..#..
.###.
.....
.#.
###
浙公网安备 33010602011771号