☰
动态规划在星空图模式匹配中的C++实现
2026/10/26 12:16:52 网站建设 项目流程

1. 题目背景与需求分析

P7133 "小P的星空"是信息学奥赛(NOI)系列题目中的一道经典动态规划问题。题目描述了一个浪漫的场景:小P在夜晚仰望星空,将星星连成不同的星座图案。我们需要用C++编写程序,计算在给定的星空图中能够组成多少个特定的星座形状。

这道题的核心考察点在于:

  • 二维矩阵中的模式匹配
  • 动态规划在图形识别中的应用
  • 空间复杂度的优化处理

实际解题时,我们需要处理以下几个关键问题:

  1. 如何高效表示星空图和星座模板
  2. 如何处理不同方向的星座变体(旋转、镜像等)
  3. 如何优化匹配算法的时间复杂度

2. 数据结构设计与输入处理

2.1 星空图的存储结构

对于N×M的星空图,最直接的存储方式是二维字符数组:

const int MAX_N = 1005; char starMap[MAX_N][MAX_N];

但在实际比赛中,考虑到内存限制和访问效率,更推荐使用vector容器:

vector<string> starMap; int n, m; // 星空图的行列数 void input() { cin >> n >> m; starMap.resize(n); for(int i=0; i<n; i++) { cin >> starMap[i]; } }

2.2 星座模板的表示

星座模板通常较小(比如3×3或5×5),可以用相同方式存储。但需要考虑其所有可能的变体:

struct Constellation { vector<string> pattern; int size; void rotate90() { // 顺时针旋转90度 vector<string> newPattern(size, string(size, ' ')); for(int i=0; i<size; i++) { for(int j=0; j<size; j++) { newPattern[j][size-1-i] = pattern[i][j]; } } pattern = newPattern; } void mirror() { // 水平镜像 for(auto &row : pattern) { reverse(row.begin(), row.end()); } } };

3. 核心算法实现

3.1 暴力匹配算法

最直接的思路是枚举星空图中的每个位置,检查是否匹配星座模板:

bool matchAt(const vector<string>& map, const vector<string>& pattern, int x, int y) { int psize = pattern.size(); for(int i=0; i<psize; i++) { for(int j=0; j<psize; j++) { if(x+i >= map.size() || y+j >= map[0].size()) return false; if(pattern[i][j] != '.' && map[x+i][y+j] != pattern[i][j]) return false; } } return true; } int countMatches(const vector<string>& map, const Constellation& c) { int count = 0; for(int i=0; i<=map.size()-c.size; i++) { for(int j=0; j<=map[0].size()-c.size; j++) { if(matchAt(map, c.pattern, i, j)) { count++; } } } return count; }

3.2 使用动态规划优化

当星座模板较大时,暴力匹配效率低下。可以采用动态规划预处理星空图:

vector<vector<int>> preprocess(const vector<string>& map, char target) { int n = map.size(), m = map[0].size(); vector<vector<int>> dp(n+1, vector<int>(m+1, 0)); for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1]; if(map[i-1][j-1] == target) { dp[i][j]++; } } } return dp; } bool fastMatch(const vector<vector<int>>& dp, const Constellation& c, int x, int y, char target) { int psize = c.size; int expected = 0; for(int i=0; i<psize; i++) { for(int j=0; j<psize; j++) { if(c.pattern[i][j] == target) expected++; } } int actual = dp[x+psize][y+psize] - dp[x][y+psize] - dp[x+psize][y] + dp[x][y]; return actual == expected; }

4. 完整解题代码实现

结合上述思路,完整的解题代码如下:

#include <iostream> #include <vector> #include <algorithm> using namespace std; struct Constellation { vector<string> pattern; int size; void rotate90() { vector<string> newPattern(size, string(size, ' ')); for(int i=0; i<size; i++) { for(int j=0; j<size; j++) { newPattern[j][size-1-i] = pattern[i][j]; } } pattern = newPattern; } void mirror() { for(auto &row : pattern) { reverse(row.begin(), row.end()); } } void generateAllVariants(vector<Constellation>& variants) { variants.push_back(*this); // 生成3个旋转版本 for(int i=0; i<3; i++) { rotate90(); variants.push_back(*this); } // 生成镜像版本 mirror(); variants.push_back(*this); // 生成镜像后的旋转版本 for(int i=0; i<3; i++) { rotate90(); variants.push_back(*this); } } }; bool matchAt(const vector<string>& map, const vector<string>& pattern, int x, int y) { int psize = pattern.size(); for(int i=0; i<psize; i++) { for(int j=0; j<psize; j++) { if(x+i >= map.size() || y+j >= map[0].size()) return false; if(pattern[i][j] != '.' && map[x+i][y+j] != pattern[i][j]) return false; } } return true; } int countMatches(const vector<string>& map, const Constellation& c) { int count = 0; for(int i=0; i<=map.size()-c.size; i++) { for(int j=0; j<=map[0].size()-c.size; j++) { if(matchAt(map, c.pattern, i, j)) { count++; } } } return count; } int main() { // 输入处理 int n, m, k; cin >> n >> m; vector<string> starMap(n); for(int i=0; i<n; i++) { cin >> starMap[i]; } cin >> k; Constellation c; cin >> c.size; c.pattern.resize(c.size); for(int i=0; i<c.size; i++) { cin >> c.pattern[i]; } // 生成所有可能的星座变体 vector<Constellation> variants; c.generateAllVariants(variants); // 去重 sort(variants.begin(), variants.end(), [](const Constellation& a, const Constellation& b) { return a.pattern < b.pattern; }); variants.erase(unique(variants.begin(), variants.end(), [](const Constellation& a, const Constellation& b) { return a.pattern == b.pattern; }), variants.end()); // 计算匹配总数 int total = 0; for(const auto& variant : variants) { total += countMatches(starMap, variant); } cout << total << endl; return 0; }

5. 算法优化与性能分析

5.1 时间复杂度分析

原始暴力算法的时间复杂度为: O(N×M×K×P²) 其中:

  • N,M是星空图尺寸
  • K是星座变体数量(最多8个)
  • P是星座模板尺寸

使用动态规划预处理后,可以优化到: O(N×M + K×N×M)

5.2 空间优化技巧

在实际比赛中,内存限制严格,可以采用以下优化:

  1. 使用bitset压缩存储星座模板
  2. 按行处理星空图,减少内存占用
  3. 复用预处理数组
// 使用bitset表示星座模板 bitset<32> patternBits[5]; // 假设最大星座尺寸为5×5 void convertToBitset(const vector<string>& pattern) { for(int i=0; i<pattern.size(); i++) { for(int j=0; j<pattern[i].size(); j++) { if(pattern[i][j] != '.') { patternBits[i].set(j); } } } }

6. 常见错误与调试技巧

6.1 边界条件处理

常见错误包括:

  1. 数组越界访问
  2. 未考虑星座模板超出星空图边界的情况
  3. 旋转和镜像操作实现错误

调试建议:

  1. 先测试小规模数据(如3×3星空图和2×2星座)
  2. 打印中间结果验证旋转/镜像是否正确
  3. 使用assert检查数组访问

6.2 输入输出优化

对于大规模数据:

  1. 使用快速IO方法
  2. 避免不必要的endl
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);

7. 测试用例设计

7.1 基础测试用例

3 3 *.* .*. *.* 1 3 *.* .*. *.*

预期输出:1(完全匹配)

7.2 旋转测试用例

4 4 ..*. .*.. *... ...* 1 3 *.. .*. ..*

预期输出:2(匹配原始和旋转版本)

7.3 边界测试用例

2 2 ** ** 1 2 *. .*

预期输出:0(虽然元素相同但模式不匹配)

8. 竞赛实战建议

  1. 模板准备:提前准备好常用数据结构(如二维数组处理、旋转操作等)的代码模板
  2. 测试驱动:先写简单的测试用例验证基本功能,再逐步增加复杂度
  3. 性能预估:根据题目约束条件估算算法复杂度,确保在时间限制内
  4. 调试输出:在关键步骤添加有条件调试输出,便于快速定位问题
#define DEBUG 1 #if DEBUG #define debug(x) cout << #x << " = " << x << endl #else #define debug(x) #endif

在解决这类图形模式匹配问题时,最重要的是建立清晰的思维模型。先考虑最简单的情况,再逐步增加复杂度。实际比赛中,通常不需要实现最优算法,而是在有限时间内写出正确且足够高效的解法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询