状态压缩搜索解法(DFS + Dominance)

一、问题回顾

给定一个 M × N 的地图:

  • 0:障碍,不可通过
  • -1:加油站,可将油量重置为 100
  • 正整数:通过该格子的油耗

车辆从左上角 (0,0) 出发,到右下角 (M-1,N-1) 结束,可上下左右移动。

要求计算 保证能到达终点的最小初始油量 ,若无法到达,返回 -1。


二、核心观察(关键建模)

1️⃣ 初始油量的真正含义

由于:

  • 加油站会 直接重置油量为 100
  • 油箱最大容量固定为 100

因此,初始油量只需要覆盖"最坏的一段连续消耗" 。

换句话说:

最小初始油量 = 路径上所有"连续消耗段"的最大值

而不是路径总油耗。


2️⃣ 路径可以拆分为若干"连续消耗段"

  • 遇到 -1:连续段结束,油量重置
  • 正整数:加入当前连续消耗段

问题转化为:

在所有可行路径中,最小化
max(每一段连续消耗)


三、状态设计

我们在 DFS 中维护如下状态:

arduino 复制代码
(y, x, min, used)

含义:

  • (y, x):当前位置
  • used:当前连续消耗段的累计油耗
  • min:到目前为止,路径上出现的 最大连续消耗段(候选答案)

终点处的答案即为:

arduino 复制代码
min === -1 ? used : min

(处理"全程无加油站"的情况)


四、剪枝与状态压缩(核心优化)

1️⃣ 基本可行性剪枝

arduino 复制代码
if (used > 100 || min > 100) return;
if (min > res) return;
  • 超过油箱上限,路径必死
  • 已经不可能优于当前最优解,直接剪枝

2️⃣ Dominance(支配)剪枝 ------ 关键创新点

对每个 (y,x),我们只保留不被支配的状态。

定义 dominance 关系:

若存在历史状态 (bMin, bUsed),使得
bMin ≤ min 且 bUsed ≤ used

则当前状态 必然更差,可安全剪枝

实现方式:

ini 复制代码
const [bMin, bUsed] = best[y][x];
if (min !== -1 && min > bMin || min === -1 && used > bMin) return;
if (min === bMin && used >= bUsed) return;
best[y][x] = [min === -1 ? bMin : min, used];

为什么这是安全的?

  • min 是最终优化目标(越小越好)
  • used 只影响后续可行性(越小越好)
  • 被支配状态 在未来不可能产生更优结果

这相当于只保留 Pareto 前沿。


五、完整算法流程

  1. 从 (0,0) 开始 DFS
  2. 维护 (min, used) 状态
  3. 遇到加油站重置 used
  4. 使用 dominance 剪枝压缩状态空间
  5. 到达终点更新答案

六、时间复杂度分析

状态规模对比

方法 每个格子状态数 总状态数
Dijkstra(fuel 维度) O(100) O(M·N·100)
本解法(Pareto 前沿) O(1)(极少) O(M·N)

总复杂度

scss 复制代码
O(M × N)

在 M,N ≤ 200 的限制下,对 JavaScript 非常友好。


七、与 Dijkstra 解法的对比

维度 Dijkstra 本解法
状态建模 (x,y,fuel) (x,y,min,used)
状态数 Θ(M·N·100) Θ(M·N)
数据结构 优先队列 DFS + 数组
JS 性能 中等 优秀
通用性 高 题目特化

本解法利用了"油箱上限 + 加油站重置"的结构性约束,

在该问题下 状态空间严格小于 Dijkstra。


八、完整代码(DFS + Dominance)

ini 复制代码
const rl = require("readline").createInterface({ input: process.stdin });
let lines = [];
rl.on("line", l => lines.push(l)).on("close", () => {
    const [M, N] = lines[0].split(',').map(Number);
    let grid = lines.slice(1).map(r => r.split(',').map(Number));

    let res = Infinity;
    let vis = Array.from({ length: M }, () => Array(N).fill(false));
    let best = Array.from({ length: M }, () =>
        Array.from({ length: N }, () => [Infinity, Infinity])
    );

    const dir = [[1,0],[0,1],[0,-1],[-1,0]];

    function dfs(y, x, min, used) {
        if (used > 100 || min > 100 || min > res) return;

        const [bMin, bUsed] = best[y][x];
        if (min !== -1 && min > bMin || min === -1 && used > bMin) return;
        if (min === bMin && used >= bUsed) return;

        best[y][x] = [min === -1 ? bMin : min, used];

        if (y === M - 1 && x === N - 1) {
            res = Math.min(res, min === -1 ? used : min);
            return;
        }

        for (let [dy, dx] of dir) {
            let ny = y + dy, nx = x + dx;
            if (ny < 0 || ny >= M || nx < 0 || nx >= N) continue;
            if (vis[ny][nx] || grid[ny][nx] === 0) continue;

            vis[ny][nx] = true;
            if (grid[ny][nx] === -1) {
                dfs(ny, nx, min === -1 ? used : min, 0);
            } else {
                dfs(ny, nx, min, used + grid[ny][nx]);
            }
            vis[ny][nx] = false;
        }
    }

    vis[0][0] = true;
    dfs(0, 0, -1, Math.max(grid[0][0], 0));

    console.log(res === Infinity ? -1 : res);
});

九、总结

  • 这是一次 问题特化下的状态压缩优化
  • 利用 Pareto dominance 将搜索空间压缩到极限
  • 在 JS 环境下 性能严格优于通用 Dijkstra
相关推荐
zyl76514 小时前
FlashAttention 详解:从 GPU 硬件、普通 Attention 到分块计算与 Online Softmax
算法
爱读书的小胖18 小时前
LeetCode 122:买卖股票的最佳时机 II(C)解题分析
算法
结局无敌18 小时前
React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
深度学习·算法·react native·react.js·harmonyos
OPEN-F19 小时前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
weixin_3077791319 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
tomwang032219 小时前
路径优化建模、优化算法分类及工程化小结
算法·数学建模·分类
小羊没烦恼!19 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
2501_9269783319 小时前
人机通信协议--人和机器之间的交互想象
人工智能·经验分享·算法·ai写作
行者全栈架构师19 小时前
【鸿蒙心迹】互动卡片全链路——把核心功能放到桌面(FormKit 实战)(HarmonyOS 7.x)
前端·算法·架构
无定义_19 小时前
10.11日常训练
算法