小红的皇后
小红的皇后
方法一:动态规划(DP)+ 分段最小值维护
状态定义
设 dp[i][j] 表示皇后从左上角 (0,0) 到达格子 (i,j) 所需的最少移动步数。
由于皇后只能向右、向下、向右下移动,因此坐标严格单调递增,状态图是一个有向无环图(DAG)。我们可以按照行从上到下、每行从左到右的顺序计算所有 dp 值。
朴素转移(会超时)
到达 (i,j) 的前驱可能是:
- 同一行左侧任意空格
(i, y'),其中y' < j且中间无障碍; - 同一列上方任意空格
(x', j),其中x' < i且中间无障碍; - 同一对角线(左上→右下方向)上的任意空格
(i-k, j-k),其中k ≥ 1且中间无障碍。
若枚举所有前驱,最坏复杂度为 O(nm(n+m)),不可接受。
优化:连续段内维护最小值
观察发现,对于同一行,障碍物会切断连续的可移动区间。在一个连续的空格段内,任意两个格子之间都可以一步到达(同一行)。因此,如果我们能快速知道当前连续段内最小的 dp 值,就能在 O(1) 时间内完成转移。
具体做法是,在扫描过程中维护三个最小值:
rowMin:当前行中,当前扫描到的连续空格段内的最小dp值;colMin[j]:第j列中,当前扫描到的连续空格段内的最小dp值;diagMin[diagIdx]:同一条对角线(i-j为定值)上,当前扫描到的连续空格段内的最小dp值。为了索引非负,令diagIdx = i - j + m。
转移过程
按 i = 0 → n-1,j = 0 → m-1 依次处理:
-
若
grid[i][j] == '*'(障碍):- 该格子不可达,且它会切断行、列、对角线上的连续段,因此将
rowMin、colMin[j]、diagMin[diagIdx]全部重置为INF。 - 跳过后续步骤。
- 该格子不可达,且它会切断行、列、对角线上的连续段,因此将
-
否则(空格):
- 若
(i,j) == (0,0),则dp[0][0] = 0。 - 否则,取
best = min(rowMin, colMin[j], diagMin[diagIdx])。- 若
best == INF,则当前格子不可达,dp[i][j] = INF。 - 否则
dp[i][j] = best + 1(因为从任意前驱一步即可到达)。
- 若
- 若
dp[i][j] < INF,则用dp[i][j]更新rowMin、colMin[j]、diagMin[diagIdx](取较小值)。
- 若
答案
最终 dp[n-1][m-1] 即为最少步数;若为 INF,输出 -1。
复杂度
- 时间复杂度:
O(nm),每个格子常数次操作。 - 空间复杂度:
O(nm)存储dp(可优化到O(m)或O(n+m),但2000×2000规模下O(nm)可接受)。
方法二:0-1 BFS(最短路径)
核心思想
将皇后的“一次移动”拆解为“沿同一方向连续走多格”的过程。如果我们把每一步(走一格)看作图上的边,则:
- 如果当前方向和下一步方向相同,说明还在同一次移动中,不增加移动次数;
- 如果方向改变,说明开始了一次新的移动,步数加 1。
这样,问题就转化为一个 0-1 边权图的最短路问题,其中边权为 0(同方向)或 1(转向)。
状态定义
状态为 (x, y, dir),其中:
(x, y)为当前所在格子;dir表示到达当前格子时,上一次移动的方向(0=向右,1=向右下,2=向下)。
初始状态
起点 (0,0) 可以向三个方向出发,第一次移动必须计为 1 步。因此,初始时,对于三个方向 dir = 0,1,2,设置 dist[0][0][dir] = 0,并将它们全部入队。但在扩展时,从起点出发,任何方向的边权强制为 1(而不是 0)。
转移
对于当前状态 (x, y, dir),尝试向三个方向 nd ∈ {0,1,2} 走一步到 (nx, ny):
- 如果
(nx, ny)超出边界或为障碍,则跳过; - 计算本次移动的代价
cost:- 如果
(x,y)是起点(0,0),则cost = 1(第一次移动必须计数); - 否则,如果
nd == dir,则cost = 0(继续同方向,仍为同一次移动); - 否则
cost = 1(转向,开始新的一次移动);
- 如果
- 新距离
ndist = dist[x][y][dir] + cost; - 如果
ndist < dist[nx][ny][nd],更新距离,并根据cost值入队:- 若
cost == 0,则使用push_front(0-1 BFS 的常规操作,优先处理 0 权边); - 若
cost == 1,则使用push_back。
- 若
答案
最终答案为 min_{dir} dist[n-1][m-1][dir],若为 INF,输出 -1。
正确性解释
- 由于同方向的连续扩展代价为 0,BFS 会一次性沿同一方向推进到障碍前或边界,等价于皇后的一次合法移动。
- 转向代价为 1,正好对应移动次数的累加。
- 0-1 BFS 保证每个状态第一次出队时距离已是最短,因此最终答案正确。
复杂度
- 状态总数:
n × m × 3。 - 每个状态转移尝试 3 个方向,因此时间复杂度
O(nm)。 - 空间复杂度:
O(nm)存储距离数组(可用uint16_t等紧凑类型)。
两种方法对比
| 方面 | DP + 分段最小值 | 0-1 BFS |
|---|---|---|
| 核心思路 | 利用 DAG 顺序,维护连续段最小 dp 值 | 将连续移动建模为 0 权边,转化为 0-1 最短路 |
| 实现复杂度 | 较简单,只需按行扫描 | 需要 BFS 队列,状态三维 |
| 空间占用 | dp[n][m] + 若干辅助数组 |
dist[n][m][3] |
| 对障碍处理 | 遇到障碍重置对应最小值 | 障碍不扩展,自然截断同向 0 权边 |
| 通用性 | 仅适用于单调方向(右、下、右下) | 可推广到更多方向或更复杂的移动规则 |
两种方法均为 O(nm),均能通过本题数据范围。读者可根据喜好选用。
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
void solve() {
int n, m; cin >> n >> m;
vector<string> grid(n);
for(int i = 0; i < n; i ++) cin >> grid[i];
vector<vector<int>> dp(n, vector<int>(m, INF));
vector<int> colMin(m, INF);
vector<int> diagMin(n + m, INF); // 对角线索引范围 [-(m-1), n-1],偏移 m 使之非负
int offset = m;
for(int i = 0; i < n; i ++) {
int rowMin = INF; // 当前行连续段最小 dp
for(int j = 0; j < m; j++) {
if(grid[i][j] == '*') {// 障碍切断连续段,重置相关最小值
rowMin = INF;
colMin[j] = INF;
diagMin[i - j + offset] = INF;
continue;
}
if(i == 0 && j == 0) dp[i][j] = 0;
else {
int best = min({rowMin, colMin[j], diagMin[i - j + offset]});
dp[i][j] = (best == INF ? INF : best + 1);
}
if(dp[i][j] < INF) {// 如果当前格子可达,更新三个最小值
rowMin = min(rowMin, dp[i][j]);
colMin[j] = min(colMin[j], dp[i][j]);
diagMin[i - j + offset] = min(diagMin[i - j + offset], dp[i][j]);
}
}
}
int ans = dp[n - 1][m - 1];
cout << (ans == INF ? -1 : ans) << '\n';
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
solve();
return 0;
}
双端队列优化,(0-1)BFS做法
#include <bits/stdc++.h>
using namespace std;
const int N = 2000, M = 2000;
const uint16_t INF = 0xFFFF;
uint16_t dist[N][M][3];
int dx[3] = {0, 1, 1};
int dy[3] = {1, 1, 0};
void solve() {
int n, m; cin >> n >> m;
vector<string> grid(n);
for (int i = 0; i < n; i ++) cin >> grid[i];
memset(dist, 0xFF, sizeof(dist));
deque<tuple<int, int, int>> dq;
for(int d = 0; d < 3; d ++) {
dist[0][0][d] = 0;
dq.push_back({0, 0, d});
}
while(!dq.empty()) {
auto [x, y, dir] = dq.front(); dq.pop_front();
uint16_t cur = dist[x][y][dir];
for(int nd = 0; nd < 3; ++nd) {
int nx = x + dx[nd], ny = y + dy[nd];
if(nx >= n || ny >= m || grid[nx][ny] == '*') continue;
uint16_t cost;
if(x == 0 && y == 0) cost = 1;
else cost = (nd == dir) ? 0 : 1;
uint16_t ndist = cur + cost;
if(ndist < dist[nx][ny][nd]) {
dist[nx][ny][nd] = ndist;
if(cost == 0) dq.push_front({nx, ny, nd});
else dq.push_back({nx, ny, nd});
}
}
}
uint16_t ans = INF;
for(int d = 0; d < 3; d ++) ans = min(ans, dist[n-1][m-1][d]);
if(ans == INF) cout << -1 << '\n';
else cout << (int)ans << '\n';
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
solve();
return 0;
}

浙公网安备 33010602011771号