状态压缩搜索解法(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 ≤ minbUsed ≤ 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
相关推荐
FakeOccupational21 小时前
【数学 密码学】量子通信:光的偏振&极化的量子不确定性特性 + 量子密钥分发 BB84算法步骤
算法·密码学
ZhengEnCi1 天前
S10-蓝桥杯 17822 乐乐的积木塔
算法
贾斯汀玛尔斯1 天前
每天学一个算法--拓扑排序(Topological Sort)
算法·深度优先
大龄程序员狗哥1 天前
第25篇:Q-Learning算法解析——强化学习中的经典“价值”学习(原理解析)
人工智能·学习·算法
exp_add31 天前
质数相关知识
算法
小辉同志1 天前
215. 数组中的第K个最大元素
数据结构·算法·leetcode··快速选择
小O的算法实验室1 天前
2025年IEEE TITS,基于矩阵的进化计算+面向无线传感器网络数据收集无人机路径规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
OidEncoder1 天前
编码器分辨率与机械精度的关系
人工智能·算法·机器人·自动化
memcpy01 天前
LeetCode 2615. 等值距离和【相同元素分组+前缀和;考虑距离和的增量】中等
算法·leetcode·职场和发展
炽烈小老头1 天前
【 每天学习一点算法 2026/04/22】四数相加 II
学习·算法