找回密码
 立即注册
首页 业界区 安全 补题日记:26-1-13

补题日记:26-1-13

哎禹供 2026-1-13 23:20:01
Table of Contents


  • 前言:
  • Problem C:P1434 [SHOI2002] 滑雪

    • 题目大意:
    • WriteUp:

      • 一、记忆化搜索
      • 二、动态规划

    • 总结:

  • 结语:
Powered by GhostFace's Emacs.

前言:

Ctorch诞生的第179天,小小的庆祝一下。
再次宣传我们团队的项目:https://github.com/ShengFlow/CTorch/tree/feature-nn.Module
这是一个纯C++写的高性能ML库,我们将会在2026.2.16(除夕夜24:00)发布第一个内测版本(RC 1),敬请期待。
这是一次比赛的赛后补题记录&题解(Part 2)。
本文由 Emacs 29.0 Org-mode 写成,在此感谢GNU做出的开源软件运动。

Problem C:P1434 [SHOI2002] 滑雪

这道题是我认为非常有价值的一道题目,它的价值不在于思维复杂,而是提供了一个「搜索与动态规划之间的桥梁」。
它告诉我们:“记忆化搜索就是动态规划的一种实现方式”,换言之,记忆化搜索与动态规划是「一体两面」的关系,任何动态规划都可以写成记忆化搜索。
我们后文会阐述这一点。

题目大意:

给定一个矩阵,寻找矩阵中的一条最长下降路径。

WriteUp:

这道题目在一本通·提高篇里仍然有。
这道题很明显应该使用搜索解决,然而,当你把一个裸的dfs提交上去时,你就会发现,这道题TLE了。
原因是什么呢?我们重复搜索了太多的点(或者说:「解空间」)。
那么怎么解决这个问题呢,我们考虑一下,是否在任意时刻,我们搜索到矩阵中 点 \(A_{i,j}\) 时,其结果都是一样的?
答案是显然的,对于任何一个点,从这个点出发的「最长下降路径长度」始终不变。
那么对于同一个点,我们就没必要在同样的结果上花费时间进行搜索。
由此,我们可以使用一个数组 \(memo\) 存储搜索过的结果,其中 \(memo_{i,j}\) 代表从 \(A_{i,j}\) 出发的「最长下降路径长度」。
正如我们一开始所说,“记忆化搜索和动态规划是一体两面的关系”,对于这道题,我们就有两种方法解决,它们是等价的。

一、记忆化搜索

如上文所述,我们需要改造一下dfs主函数,使其返回一个 \(int\) 型的值,代表从 \(A_{i,j}\) 出发的「最长下降路径长度」。
随后,在每次搜索之前,我们需要检查一下 \(memo\) 数组中是否存在结果,如果存在,直接返回,不再搜索。
这个套路是通用的,当我们发现问题的 「重叠子问题」 时,我们就可以使用记忆化搜索。
代码如下:
[code]#include using namespace std;static const int MAXN = 105;static int height[MAXN][MAXN];static int memo[MAXN][MAXN];  static int n, m;static const int dx[] = {0, 0, 1, -1};static const int dy[] = {1, -1, 0, 0};// 自顶向下的记忆化搜索static int dfs(int x, int y) {    int& res = memo[x][y];      // 这里使用了引用,即“&”,它不会拷贝变量,更省内存,尤其是在使用vector这种容器时,拷贝会极其费时费力。     if (res != -1) return res;  // 已计算过则直接返回    res = 1;  // 至少包含当前节点    for (int dir = 0; dir < 4; ++dir) {        const int nx = x + dx[dir];        const int ny = y + dy[dir];        // 越界检查        if (nx < 1 || nx > n || ny < 1 || ny > m) continue;        // 只能向更低处移动        if (height[x][y] > n >> m)) return 0;    for (int i = 1; i  height[j];        }    }    // 初始化 memo 为 -1 表示未访问    memset(memo, -1, sizeof(memo));    int answer = 0;    for (int i = 1; i > n >> m)) return 0;    priority_queue pq;    for (int i = 1; i  height[j];            dp[j] = 1;  // 初始状态:至少包含自身            pq.push({i, j, height[j]});        }    }    int answer = 0;    while (!pq.empty()) {        auto [x, y, cur_h] = pq.top();        pq.pop();        const int current_dp = dp[x][y];        answer = max(answer, current_dp);        // 尝试向四个方向扩展(从低到高处理,所以是反向传播)        const int dx[] = {-1, 1, 0, 0};  // 上下左右        const int dy[] = {0, 0, -1, 1};        for (int dir = 0; dir < 4; ++dir) {            const int nx = x + dx[dir];            const int ny = y + dy[dir];            // 越界检查            if (nx < 1 || nx > n || ny < 1 || ny > m) continue;            // 只能从更高的邻居转移过来(反向思考:当前点更新其高处邻居)            if (height[nx][ny]

相关推荐

2026-1-21 09:04:47

举报

2026-1-22 03:56:18

举报

2026-1-26 08:37:44

举报

2026-1-28 22:42:30

举报

2026-1-29 02:19:25

举报

2026-1-31 16:00:17

举报

2026-2-9 18:45:51

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-11 09:23:21

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-11 14:19:22

举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册