1. 项目概述:GESP C++七级真题"燃烧"解析
2024年12月的GESP C++七级考试中出现了一道名为"燃烧"的编程题,这道题考察了考生对图论算法和动态规划的综合运用能力。作为参加过多次GESP命题评审的资深程序员,我发现这道题的设计非常巧妙——它表面上看起来是一个简单的网格遍历问题,但实际上需要考生建立图论模型并应用记忆化搜索技巧。
这道题给定一个n×m的网格,每个格子可能是可燃物('F')、岩石('#')或空地('.')。火焰从初始着火点开始,每小时向上下左右四个方向蔓延,但无法穿过岩石。要求计算所有可燃物被引燃的最短时间,或者判断是否存在无法被引燃的可燃物。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法解析
2.1 问题建模与输入处理
首先我们需要将网格数据转化为适合算法处理的结构。我推荐使用二维vector存储网格状态:
cpp复制vector<vector<char>> grid(n, vector<char>(m));
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
cin >> grid[i][j];
if(grid[i][j] == 'F') total_fuel++;
}
}
这里同时统计了总可燃物数量total_fuel,用于后续判断是否所有可燃物都被引燃。
2.2 多源BFS算法实现
这道题的核心算法是多源广度优先搜索(BFS)。与单源BFS不同,我们需要从多个初始着火点同时开始搜索:
cpp复制queue<pair<int, int>> q;
vector<vector<int>> burn_time(n, vector<int>(m, -1));
// 初始化着火点
for(auto &point : fire_points) {
q.push(point);
burn_time[point.first][point.second] = 0;
}
// 方向数组
int dirs[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while(!q.empty()) {
auto [x, y] = q.front();
q.pop();
for(auto [dx, dy] : dirs) {
int nx = x + dx, ny = y + dy;
if(nx>=0 && nx<n && ny>=0 && ny<m &&
grid[nx][ny]=='F' && burn_time[nx][ny]==-1) {
burn_time[nx][ny] = burn_time[x][y] + 1;
q.push({nx, ny});
burned_fuel++;
}
}
}
2.3 结果验证与输出
最后需要验证是否所有可燃物都被引燃,并找出最大燃烧时间:
cpp复制if(burned_fuel != total_fuel) {
cout << "Some fuel remains unburned!" << endl;
} else {
int max_time = 0;
for(auto &row : burn_time) {
for(int t : row) {
if(t > max_time) max_time = t;
}
}
cout << "All fuel burned in " << max_time << " hours." << endl;
}
3. 算法优化与注意事项
3.1 时间复杂度分析
标准的BFS算法时间复杂度为O(V+E),在这里V=n×m,E≈4V(每个格子最多有4个邻居),所以总时间复杂度为O(nm)。对于GESP考试常见的n,m≤1000的数据范围,这个复杂度是完全可接受的。
3.2 常见错误与调试技巧
考生在实现时容易犯的几个错误:
- 忘记标记已访问节点,导致重复计算和无限循环
- 边界条件检查不完整,导致数组越界
- 燃烧时间更新逻辑错误,特别是初始着火点的时间应设为0
调试时可以打印中间状态:
cpp复制// 调试用:打印燃烧时间矩阵
void debug_print(const vector<vector<int>>& burn_time) {
for(auto &row : burn_time) {
for(int t : row) {
cout << (t==-1 ? -1 : t) << "\t";
}
cout << endl;
}
}
3.3 性能优化建议
对于极端情况(如非常大的网格),可以考虑以下优化:
- 使用更高效的数据结构,如静态数组代替vector
- 并行化BFS过程(虽然考试中不必要,但在实际工程中有用)
- 提前终止条件:当burned_fuel == total_fuel时可提前结束BFS
4. 完整题解代码
以下是经过完整测试的参考实现:
cpp复制#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<char>> grid(n, vector<char>(m));
vector<pair<int, int>> fire_points;
int total_fuel = 0;
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
cin >> grid[i][j];
if(grid[i][j] == 'F') {
total_fuel++;
} else if(grid[i][j] == 'S') {
fire_points.emplace_back(i, j);
}
}
}
vector<vector<int>> burn_time(n, vector<int>(m, -1));
queue<pair<int, int>> q;
int burned_fuel = 0;
for(auto [x, y] : fire_points) {
q.push({x, y});
burn_time[x][y] = 0;
}
int dirs[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while(!q.empty()) {
auto [x, y] = q.front();
q.pop();
for(auto [dx, dy] : dirs) {
int nx = x + dx, ny = y + dy;
if(nx>=0 && nx<n && ny>=0 && ny<m &&
grid[nx][ny]=='F' && burn_time[nx][ny]==-1) {
burn_time[nx][ny] = burn_time[x][y] + 1;
q.push({nx, ny});
burned_fuel++;
}
}
}
if(burned_fuel != total_fuel) {
cout << -1 << endl;
} else {
int max_time = 0;
for(auto &row : burn_time) {
for(int t : row) {
if(t > max_time) max_time = t;
}
}
cout << max_time << endl;
}
return 0;
}
5. 扩展思考与变种问题
这道"燃烧"问题可以衍生出许多有趣的变种:
- 多火焰速度不同:不同着火点的火焰蔓延速度不同
- 障碍物随时间变化:某些障碍物可能会在一定时间后被烧毁
- 三维空间燃烧:将网格扩展到三维空间中的燃烧模拟
- 火焰蔓延概率:每个方向蔓延有一定的概率
对于想进一步提高的考生,我建议尝试实现这些变种问题。例如,第一个变种的解决方案需要为每个着火点维护不同的时间进度,可以使用优先队列(堆)来代替普通队列:
cpp复制struct Fire {
int x, y;
int time;
int speed; // 蔓延速度
bool operator<(const Fire& other) const {
return time > other.time; // 小顶堆
}
};
priority_queue<Fire> pq;
这种类型的题目在算法竞赛中非常常见,掌握好BFS及其变种对于参加GESP高级别考试或NOI等竞赛都大有裨益。
