ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

蓝桥杯登山问题:动态规划与Dijkstra算法实战

蓝桥杯登山问题:动态规划与Dijkstra算法实战

1. 题目背景与需求分析

2025年第十六届蓝桥杯省赛真题"登山"是一道典型的算法设计题,主要考察选手对动态规划、贪心算法等核心算法的掌握程度。题目描述通常为:给定一个表示山脉高度的二维矩阵,选手需要找到从起点到终点的最优登山路径,可能涉及路径长度、体力消耗或时间成本等约束条件。

这类题目在蓝桥杯竞赛中属于中等偏上难度,往往需要选手:

  • 准确理解二维矩阵表示的地形特征
  • 设计合理的状态转移方程
  • 处理边界条件和特殊地形
  • 优化算法时间复杂度

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

2.1 地形矩阵存储

使用二维数组存储山脉高度是最直接的方案:

#define MAX_N 1005 int height[MAX_N][MAX_N]; // 山脉高度矩阵 int n, m; // 矩阵行列数

输入处理示例:

void input() { scanf("%d%d", &n, &m); for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { scanf("%d", &height[i][j]); } } }

2.2 辅助数据结构

根据题目具体要求,可能需要以下辅助结构:

  • 距离矩阵:记录到达每个点的最优值
  • 路径矩阵:记录路径来源
  • 访问标记:防止重复计算
int dist[MAX_N][MAX_N]; // 距离矩阵 int path[MAX_N][MAX_N]; // 路径记录 bool visited[MAX_N][MAX_N];// 访问标记

3. 核心算法实现

3.1 动态规划解法

对于大多数登山类问题,动态规划是首选方案。我们定义状态dp[i][j]表示到达点(i,j)时的最优值(如最小体力消耗)。

3.1.1 状态转移方程

基本状态转移方程可能形如:

dp[i][j] = min( dp[i-1][j] + abs(height[i][j] - height[i-1][j]), // 从上向下 dp[i][j-1] + abs(height[i][j] - height[i][j-1]), // 从左向右 // 其他可能方向... )
3.1.2 初始化与边界处理

边界条件需要特别注意:

// 初始化 memset(dp, 0x3f, sizeof(dp)); // 设为极大值 dp[0][0] = 0; // 起点 // 边界处理 for(int i = 1; i < n; i++) { dp[i][0] = dp[i-1][0] + abs(height[i][0] - height[i-1][0]); } for(int j = 1; j < m; j++) { dp[0][j] = dp[0][j-1] + abs(height[0][j] - height[0][j-1]); }

3.2 优先队列优化(Dijkstra变种)

当问题需要考虑复杂地形时,可以使用优先队列优化:

struct Node { int x, y; int cost; bool operator<(const Node& other) const { return cost > other.cost; // 小顶堆 } }; void dijkstra() { priority_queue<Node> pq; pq.push({0, 0, 0}); dist[0][0] = 0; while(!pq.empty()) { Node curr = pq.top(); pq.pop(); if(visited[curr.x][curr.y]) continue; visited[curr.x][curr.y] = true; // 处理四个方向 int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, -1, 1}; for(int i = 0; i < 4; i++) { int nx = curr.x + dx[i]; int ny = curr.y + dy[i]; if(nx >= 0 && nx < n && ny >= 0 && ny < m) { int new_cost = curr.cost + abs(height[nx][ny] - height[curr.x][curr.y]); if(new_cost < dist[nx][ny]) { dist[nx][ny] = new_cost; pq.push({nx, ny, new_cost}); } } } } }

4. 完整代码实现与测试

4.1 完整动态规划解法

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX_N 1005 int height[MAX_N][MAX_N]; int dp[MAX_N][MAX_N]; int n, m; void input() { scanf("%d%d", &n, &m); for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { scanf("%d", &height[i][j]); } } } int solve() { // 初始化 memset(dp, 0x3f, sizeof(dp)); dp[0][0] = 0; // 边界处理 for(int i = 1; i < n; i++) { dp[i][0] = dp[i-1][0] + abs(height[i][0] - height[i-1][0]); } for(int j = 1; j < m; j++) { dp[0][j] = dp[0][j-1] + abs(height[0][j] - height[0][j-1]); } // 动态规划 for(int i = 1; i < n; i++) { for(int j = 1; j < m; j++) { int from_top = dp[i-1][j] + abs(height[i][j] - height[i-1][j]); int from_left = dp[i][j-1] + abs(height[i][j] - height[i][j-1]); dp[i][j] = MIN(from_top, from_left); } } return dp[n-1][m-1]; } int main() { input(); printf("%d\n", solve()); return 0; }

4.2 测试用例设计

有效测试用例应包含:

  1. 常规测试:标准山脉地形
  2. 边界测试:单行/单列矩阵
  3. 极值测试:极高/极低海拔
  4. 特殊地形:悬崖/平台等

示例测试用例:

3 3 1 2 3 4 5 6 7 8 9

预期输出:8(路径:1→2→3→6→9)

5. 算法优化与扩展

5.1 空间复杂度优化

可以优化为滚动数组降低空间复杂度:

int dp[2][MAX_N]; // 只保留两行 int solve_optimized() { int curr = 0; dp[curr][0] = 0; for(int j = 1; j < m; j++) { dp[curr][j] = dp[curr][j-1] + abs(height[0][j] - height[0][j-1]); } for(int i = 1; i < n; i++) { curr ^= 1; // 切换行 dp[curr][0] = dp[curr^1][0] + abs(height[i][0] - height[i-1][0]); for(int j = 1; j < m; j++) { int from_top = dp[curr^1][j] + abs(height[i][j] - height[i-1][j]); int from_left = dp[curr][j-1] + abs(height[i][j] - height[i][j-1]); dp[curr][j] = MIN(from_top, from_left); } } return dp[curr][m-1]; }

5.2 多条件约束扩展

如果题目增加体力限制等条件,可以:

  1. 增加状态维度(如剩余体力)
  2. 使用三维动态规划
  3. 调整状态转移方程
#define MAX_ENERGY 1005 int dp[MAX_N][MAX_N][MAX_ENERGY]; int solve_with_energy(int max_energy) { // 初始化三维dp数组 // 更复杂的状态转移 // ... }

6. 常见错误与调试技巧

6.1 典型错误类型

  1. 边界条件处理不当
  2. 状态转移方程错误
  3. 数组越界访问
  4. 初始化不完整
  5. 方向处理遗漏

6.2 调试方法

  1. 打印中间状态:
void print_dp() { for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { printf("%d ", dp[i][j]); } printf("\n"); } }
  1. 单元测试:对每个函数单独测试
  2. 小规模测试:先用小矩阵验证
  3. 路径回溯:打印最优路径
void print_path() { int i = n-1, j = m-1; printf("Path: (%d,%d)", i, j); while(i != 0 || j != 0) { if(i > 0 && dp[i][j] == dp[i-1][j] + abs(height[i][j] - height[i-1][j])) { i--; } else { j--; } printf(" <- (%d,%d)", i, j); } printf("\n"); }

7. 竞赛实战建议

  1. 时间分配:建议在30-40分钟内完成此类题目
  2. 代码模板:提前准备常用算法模板
  3. 测试策略:
    • 先验证小样例
    • 再测试边界情况
    • 最后随机生成测试数据
  4. 优化顺序:
    • 先保证正确性
    • 再考虑时间复杂度
    • 最后优化空间复杂度
  5. 调试技巧:
    • 使用printf调试
    • 分模块验证
    • 画图辅助理解

8. 类似题目拓展训练

  1. 最小路径和:简单版登山问题
  2. 滑雪问题:寻找最长递减路径
  3. 地下城游戏:带有生命值约束
  4. 不同路径II:带有障碍物版本
  5. 最大子矩阵:二维扩展问题

建议在LeetCode、洛谷等平台练习这些相似题目,巩固动态规划在二维矩阵中的应用能力。每个题目至少实现两种解法(如递归+记忆化,迭代动态规划),并比较它们的性能差异。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进