当前位置:首页 > 随笔 > 正文内容

线性动态规划的空间压缩技巧

访客 随笔 2026年8月16日 1

在竞赛或工程实践中,三维甚至更高维的 DP 往往带来内存与时间双重压力。下面用两道典型例题演示如何把 O(n³) 的状态压缩到 O(n²) 乃至 O(n)。

例题一:河上漂流

一条船初始位于距下游 d 米处,共有 t 秒,每秒可以选择:

  • 消耗 1 点体力向前划 1 米;
  • 不消耗体力,被水流冲回 1 米。

体力上限为 m,问 t 秒后体力恰好为 0 且未进入峡谷(距离 > 0)的方案数。

朴素思路

dp[i][j][k]  // 第 i 秒,距下游 j 米,剩余体力 k 的方案数

状态量 O(t·(d+m)·m) ≈ 3e10,显然爆炸。

关键观察

设共划了 x 次,则后退了 t-x 次,最终坐标

d + x - (t - x) = d + 2x - t

要满足 d + 2x - t > 0,且 x ≤ m。因此坐标 j 可由 x 唯一确定,不必再存。

二维转移

f[i][j]  // 前 i 秒已用 j 次体力,且船未掉下悬崖的方案数
转移:
f[i][j] = f[i-1][j] + f[i-1][j-1]   // 后退 or 前进
答案:
x = m 时,若 d + 2x - t > 0,则累加 f[t][x]

核心代码:

const int MOD = 1e9+7;
LL dp[3005][1505];          // t ≤ 3000, m ≤ 1500
int main() {
    LL d, t, m; cin >> d >> t >> m;
    dp[0][0] = 1;
    for (int i = 1; i <= t; ++i)
        for (int j = 0; j <= m; ++j) {
            dp[i][j] = dp[i-1][j];
            if (j) dp[i][j] = (dp[i][j] + dp[i-1][j-1]) % MOD;
        }
    LL ans = 0;
    for (int x = 0; x <= m; ++x)
        if (d + 2*x - t > 0) ans = (ans + dp[t][x]) % MOD;
    cout << ans;
}

例题二:模意义下的概率

给定 n 个数 ai 及其被选中的概率 pi,求选出的子集元素和 ≡ 0 (mod m) 的概率。

背包 DP

g[i][r]  // 前 i 个数,和 mod m = r 的概率
g[i][r] = g[i-1][r]*(1-p[i]) + g[i-1][(r-a[i]%m+m)%m]*p[i]

滚动数组

仅保留当前行即可:

LL pre[M], cur[M];        // M = 1010
pre[0] = 1;
for (int i = 1; i <= n; ++i) {
    for (int r = 0; r < m; ++r) {
        int t = (r - a[i]%m + m) % m;
        cur[r] = (pre[r]*(1 - p[i] + MOD) + pre[t]*p[i]) % MOD;
    }
    swap(pre, cur);
}
cout << pre[0];

例题三:2×n 网格刷漆

从任意格子出发,每次只能走到相邻未涂色格子,求涂满 2×n 网格的方案数。

  • f[i]:涂满前 i 列且回到起点的方案数;
  • g[i]:涂满前 i 列且停在另一行的方案数。

递推:

g[i] = 2 * g[i-1] % MOD;
f[i] = (g[i] + 2*f[i-1] + 4*f[i-2]) % MOD;

总答案 = 4·f[n] + 4·Σi=2..n-1 (f[i-1]·g[n-i+1] + f[n-i]·g[i])

完整实现:

const int MOD = 1e9+7;
LL f[1010], g[1010];
int main() {
    int n; cin >> n;
    g[1] = 1; g[2] = 2;
    f[1] = 1; f[2] = 6;
    for (int i = 3; i <= n; ++i) {
        g[i] = g[i-1]*2 % MOD;
        f[i] = (g[i] + 2*f[i-1] + 4*f[i-2]) % MOD;
    }
    LL ans = 4*f[n] % MOD;
    for (int i = 2; i < n; ++i)
        ans = (ans + 4*(f[i-1]*g[n-i+1] + f[n-i]*g[i])) % MOD;
    cout << ans;
}

小结

高维 DP 的压缩通常遵循两条路线:

  1. 发现某一维可由其余维度唯一确定,直接剔除;
  2. 若维度仅用于"继承上一阶段",则使用滚动数组swap 技巧将 O(n) 空间降至 O(1)。

相关文章

可以按小时收费的VPS

很多 VPS 提供商都支持 按小时计费(hourly billing),想短期试用 / 临时搭建节点、测试网络、短期项目等场景非常合适。下面是当前最主流且靠谱的按小时 VPS 选项,分别按不同需求场景整理: 1. Vultr(全球节点,包括日本) 按小时计费 可选机房:东京 / 大阪 / 洛杉矶 / 法兰克福 / 伦敦 … 支持 PayPal(部分情况),但更常用信用卡/PayPal+卡价格参考$...

在 iPhone 上下载国外App

地区/国家限制App Store 会根据 Apple ID 的国家或地区限制应用下载。如果你的 Apple ID 绑定的是中国大陆,就可能无法下载 OpenAI 官方的 ChatGPT 应用,因为它在大陆 App Store 不上架。解决办法:换成美国、加拿大、香港等地区的 Apple ID。或者在现有 Apple ID 上更改地区。注册一个国外 Apple ID(推荐)比如注册 美国区 Appl...

Node.js 中的异步编程:回调与 Promise

Node.js 是一个基于 JavaScript 构建的单线程、非阻塞运行环境,它通过异步编程机制来高效处理多个操作。在执行如文件读取、API 请求或数据库查询等任务时,Node.js 不会等待这些操作完成,而是使用回调函数和 Promise 来避免阻塞主线程。 回调方式实现异步 那么当异步操作完成后,Node.js 如何知道接下来要做什么呢?这就要用到 回调函数(callback)。 回调本质上...

Selenium自动化测试入门指南

Selenium自动化测试入门指南

什么是自动化测试? 自动化测试是指利用软件工具自动执行测试用例,模拟用户操作,如打开网页、点击链接、输入文本等,并验证结果是否符合预期。 其主要优点包括: 大幅减少人工成本 测试速度快 可以在非工作时间运行 支持持续集成和交付 然而,它也存在一些局限性,例如开发成本较高、不适合快速变化的项目、依赖稳定的UI界面等。 自动化测试的应用条件 适合引入自动化测试的情况包括: 手动测试耗时且需要大量...

MariaDB Galera集群故障快速恢复指南

OpenStack控制节点采用三节点MariaDB Galera集群架构。当数据库集群因故障重启时,有时会出现Galera集群无法正常启动的问题。虽然有多种方法可以恢复数据库服务,但如何实现快速启动同时确保数据完整性呢? 通过分析日志发现,MariaDB Galera集群节点宕机时会在日志中输出以下信息: [Note] WSREP: 新集群视图:全局状态: 874d8e7e-5980-11e8-8...

Android 中 EventBus 的通信机制与实现原理深度解析

EventBus 核心设计思想 EventBus 是一个基于观察者模式的事件总线框架,广泛应用于 Android 平台以实现组件解耦。它通过中心化的消息分发机制,使不同层级、不同线程的对象能够以"发布-订阅"方式通信,避免了传统接口回调或广播带来的强依赖问题。 核心角色说明 事件(Event):任意 Java 对象,作为数据载体,如网络状态变更通知、用户登录信息等。 发布者(Publi...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。