题目描述
这是一个关于重叠正方形的拼图问题。给定一个目标图案,我们需要判断是否可以通过在 4×44 \times 44×4 的网格上放置 111 到 666 个(包含)2×22 \times 22×2 的实心正方形来形成该图案。
每个 2×22 \times 22×2 正方形在字符网格中表现为 333 行 555 列的边框结构:
- 水平边框用
'_'表示 - 垂直边框用
'|'表示 - 内部用空格填充
正方形可以相互重叠,当放置新正方形时,它会覆盖之前正方形在该区域的部分边框。
输入格式
输入包含多个测试用例。每个测试用例包含 555 行,每行包含 999 个字符。字符只能是:
'_':水平边框'|':垂直边框' ':空格'#':行结束标记(不是图案的一部分)
最后一个测试用例后跟一个单独的 000,表示输入结束。
输出格式
对于每个测试用例,输出用例编号和 "Yes" 或 "No",表示是否可以用 111 到 666 个正方形形成目标图案。
题目分析
问题本质
这个问题本质上是一个模式匹配问题,但难点在于:
- 重叠效应:正方形重叠时,新正方形会覆盖旧正方形的边框
- 多种组合:最多 666 个正方形,在 999 个可能位置上的组合数量很大
- 边框可见性:需要正确模拟物理上的覆盖规则
关键观察
- 网格结构:4×44 \times 44×4 的物理网格对应 555 行 999 列的字符网格
- 正方形位置:2×22 \times 22×2 正方形有 3×3=93 \times 3 = 93×3=9 个可能的放置位置
- 覆盖规则:
- 新正方形的边框总是可见
- 上边界的空格位置需要特殊处理,保留可能穿过的竖边
- 其他区域直接覆盖
解题思路
方法选择:预处理 + 精确匹配
由于测试用例可能较多,我们采用预处理生成所有可能图案 + 查询时精确匹配的策略。
算法步骤
1. 预处理阶段:生成所有可能图案
使用 DFS\texttt{DFS}DFS 回溯生成所有可能的正方形组合:
- 状态空间:最多 666 个正方形,每个可在 999 个位置中的任意位置
- 状态表示:当前网格状态
- 终止条件:放置了 666 个正方形
- 优化:每放置一个正方形就记录当前图案
2. 标准化处理
为确保图案比较的一致性,对所有生成的图案进行标准化:
- 提取包含非空格字符的最小矩形区域
- 将该区域平移到网格的左上角
3. 查询阶段
对每个测试用例:
- 标准化输入图案
- 在预生成的图案集合中查找
- 输出匹配结果
复杂度分析
- 时间复杂度:
- 预处理:O(96×标准化时间)O(9^6 \times \text{标准化时间})O(96×标准化时间),实际由于重叠和标准化,重复图案很多
- 查询:O(1)O(1)O(1) 哈希查找
- 空间复杂度:O(唯一图案数量)O(\text{唯一图案数量})O(唯一图案数量),实践中远小于理论最大值
关键实现细节
- 智能覆盖逻辑:正确处理正方形重叠时的边框可见性
- 字符串表示:使用完整字符串而非整数编码,避免哈希冲突
- 标准化:确保图案比较的一致性
代码实现
// Overlapping Squares
// UVa ID: 12113
// Verdict: Accepted
// Submission Date: 2025-11-28
// UVa Run Time: 0.130s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 存储所有可能的图案(使用字符串表示,避免编码冲突)
unordered_set<string> allPatterns;
// 标准2x2正方形的字符模板(3行5列)
const char squareTemplate[3][5] = {
{' ', '_', ' ', '_', ' '}, // 上边框:两条横线,中间有空格
{'|', ' ', ' ', ' ', '|'}, // 中间行:左右竖边,内部空格
{'|', '_', ' ', '_', '|'} // 下边框:左右竖边和两条横线
};
// 将5x9网格转换为字符串表示
string patternToString(const char grid[5][10]) {
string result;
for (int i = 0; i < 5; i++)
for (int j = 0; j < 9; j++)
result += grid[i][j];
return result;
}
// 在指定位置放置一个2x2正方形
void placeSquare(char grid[5][10], int pos) {
int row = pos / 3; // 计算行位置(0-2)
int col = pos % 3; // 计算列位置(0-2)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
int gridRow = row + i;
int gridCol = col * 2 + j; // 列位置乘以2,因为每个正方形水平占5个字符
if (gridRow >= 5 || gridCol >= 9) continue;
char templateChar = squareTemplate[i][j];
char currentChar = grid[gridRow][gridCol];
if (i == 0) { // 上边界特殊处理
if (templateChar == '_')
grid[gridRow][gridCol] = '_'; // 横线直接覆盖
else if (currentChar == '|')
; // 保留竖边(竖边穿过上边界)
else
grid[gridRow][gridCol] = ' '; // 其他情况用空格覆盖
} else {
// 中间行和下边界:直接用模板字符覆盖
grid[gridRow][gridCol] = templateChar;
}
}
}
}
// 标准化图案:提取有效区域并靠上靠左放置
void normalizePattern(char grid[5][10]) {
int minRow = 5, maxRow = -1, minCol = 9, maxCol = -1;
// 找到包含非空格字符的最小矩形区域
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 9; j++) {
if (grid[i][j] != ' ') {
minRow = min(minRow, i);
maxRow = max(maxRow, i);
minCol = min(minCol, j);
maxCol = max(maxCol, j);
}
}
}
if (minRow > maxRow || minCol > maxCol) return; // 没有有效字符
// 创建新的标准化网格
char normalized[5][10];
memset(normalized, ' ', sizeof(normalized));
// 将有效区域平移到左上角
for (int i = minRow; i <= maxRow; i++) {
for (int j = minCol; j <= maxCol; j++) {
int newI = i - minRow;
int newJ = j - minCol;
if (newI < 5 && newJ < 9)
normalized[newI][newJ] = grid[i][j];
}
}
memcpy(grid, normalized, sizeof(normalized));
}
// DFS回溯生成所有可能的图案组合(最多6个正方形)
void generatePatterns(char grid[5][10], int count) {
// 标准化当前图案并存储
char normalized[5][10];
memcpy(normalized, grid, sizeof(normalized));
normalizePattern(normalized);
allPatterns.insert(patternToString(normalized));
if (count == 6) return; // 最多放置6个正方形
// 尝试在所有9个可能位置放置正方形
for (int i = 0; i < 9; i++) {
// 保存当前状态用于回溯
char backup[5][10];
memcpy(backup, grid, sizeof(backup));
// 放置正方形并继续搜索
placeSquare(grid, i);
generatePatterns(grid, count + 1);
// 回溯:恢复之前的状态
memcpy(grid, backup, sizeof(backup));
}
}
// 预生成所有可能的图案
void generateAllPatterns() {
char grid[5][10];
memset(grid, ' ', sizeof(grid)); // 初始化为全空格
generatePatterns(grid, 0);
}
// 标准化输入的目标图案
void normalizeTarget(char target[5][10]) {
char processed[5][10];
memset(processed, ' ', sizeof(processed));
// 去除输入中的#号,只保留有效字符
for (int i = 0; i < 5; i++)
for (int j = 0; j < 9 && target[i][j] != '#'; j++)
processed[i][j] = target[i][j];
// 标准化处理后的图案
normalizePattern(processed);
memcpy(target, processed, sizeof(processed));
}
int main() {
// 预处理:生成所有可能的图案
generateAllPatterns();
int caseNum = 1;
string line;
// 处理每个测试用例
while (true) {
getline(cin, line);
if (line == "0") break; // 输入0结束
// 读取目标图案
char target[5][10];
memset(target, ' ', sizeof(target));
for (int i = 0; i < 5; i++) {
if (i > 0) getline(cin, line);
for (int j = 0; j < 9; j++)
target[i][j] = line[j];
}
// 标准化目标图案并在预生成的图案集合中查找
normalizeTarget(target);
string targetStr = patternToString(target);
bool found = allPatterns.count(targetStr);
// 输出结果
cout << "Case " << caseNum++ << ": " << (found ? "Yes" : "No") << endl;
}
return 0;
}
总结
本题的关键在于正确模拟正方形重叠的物理效果,并通过预处理优化查询性能。使用 DFS\texttt{DFS}DFS 回溯生成所有可能组合,配合标准化处理和字符串精确匹配,既保证了正确性又获得了良好的性能。
这种预处理 + 查询的模式在解决此类组合搜索问题时非常有效,特别是当测试用例较多时,能够将计算密集型工作转移到预处理阶段,显著提高整体效率。
321

被折叠的 条评论
为什么被折叠?



