题解:AtCoder AT_awc0007_e Warehouse Robot Delivery
【题目来源】
AtCoder:E - Warehouse Robot Delivery
【题目描述】
Takahashi is a programmer for delivery robots working in a large warehouse.
高桥是在一个大型仓库工作的送货机器人程序员。
The warehouse floor is divided into an \(N \times N\) grid. The cell in the \(r\)-th row from the top \((1 \leq r \leq N)\) and the \(c\)-th column from the left \((1 \leq c \leq N)\) is assigned the number \((r-1) \times N + c\). That is, the cells are numbered \(1, 2, \ldots, N\) from left to right in row \(1\), then \(N+1, N+2, \ldots, 2N\) from left to right in row \(2\), and so on. There are no walls or obstacles on the grid, and all cells are passable.
仓库地面被划分成一个 \(N × N\) 的网格。从上往下第 \(r\) 行(\(1 ≤ r ≤ N\))和从左往右第 \(c\) 列(\(1 ≤ c ≤ N\))的单元格被赋予编号 \((r-1) × N + c\)。也就是说,单元格的编号方式为:在第 \(1\) 行中从左到右编号 \(1, 2, …, N\),然后在第 2 行中从左到右编号 \(N+1, N+2, …, 2N\),依此类推。网格上没有墙壁或障碍物,所有单元格均可通行。
The delivery robot is currently waiting at cell \(S\). Takahashi needs to move the robot to the shipping area at cell \(T\). \(S\) and \(T\) are different cells. In one move, the robot can move one cell to an adjacent cell in one of the four directions (up, down, left, right). However, the robot cannot move outside the grid.
送货机器人当前在单元格 \(S\) 处等待。高桥需要将机器人移动到位于单元格 \(T\) 的发货区。\(S\) 和 \(T\) 是不同的单元格。在单次移动中,机器人可以向四个方向(上、下、左、右)之一移动到相邻的单元格。但机器人不能移动到网格外。
During its movement, the robot must collect packages from \(M\) designated shelves. The cell numbers \(P_1, P_2, \ldots, P_M\) where the shelves with packages to collect are located are given. These \(M\) cells may be visited in any order, but all of them must be visited at least once before reaching cell \(T\). Note that passing through the same cell multiple times during movement is allowed.
在移动过程中,机器人必须从 \(M\) 个指定的货架上收取包裹。给出了存放待收取包裹的货架所在的单元格编号 \(P_1, P_2, …, P_M\)。这 \(M\) 个单元格可以以任意顺序访问,但在到达单元格 \(T\) 之前,所有单元格都必须至少访问一次。注意,允许在移动过程中多次经过同一个单元格。
Find the minimum number of moves required for the robot to start from cell \(S\), visit all \(M\) designated cells, and reach cell \(T\). Here, the number of moves refers to the total number of times the robot moves to an adjacent cell.
求机器人从单元格 \(S\) 出发,访问所有 \(M\) 个指定单元格,并到达单元格 \(T\) 所需的最少移动次数。此处,移动次数指机器人移动到相邻单元格的总次数。
【输入】
\(N\) \(M\)
\(S\) \(T\)
\(P_1\) \(P_2\) \(\cdots\) \(P_M\)
- The first line contains an integer \(N\) representing the side length of the grid and an integer \(M\) representing the number of cells to visit, separated by a space.
- The second line contains an integer \(S\) representing the starting cell number and an integer \(T\) representing the destination cell number, separated by a space.
- The third line contains integers \(P_1, P_2, \ldots, P_M\) representing the cell numbers to visit, separated by spaces. If \(M = 0\), the third line is not given.
【输出】
Print in one line the minimum number of moves required for the robot to start from cell \(S\), visit all designated cells, and reach cell \(T\).
【输入样例】
3 2
1 9
5 3
【输出样例】
6
【核心思想】
-
问题分析:在 \(N \times N\) 的无障碍网格中,机器人从起点 \(S\) 出发,需访问 \(M\) 个指定的中间点 \(P_1, \ldots, P_M\)(顺序任意),最后到达终点 \(T\)。由于网格无墙壁且只能四方向移动,任意两点间的最短路径即为曼哈顿距离。问题转化为:给定 \(M+2\) 个点(起点、\(M\) 个中间点、终点),求从起点出发经过所有中间点到达终点的最短路径长度。这是一个典型的旅行商问题(TSP) 变体,因 \(M\) 较小,适合用状态压缩动态规划求解。
-
算法选择:
- 曼哈顿距离预处理:对于无障碍网格,两点 \((x_1, y_1)\) 和 \((x_2, y_2)\) 的最短距离为 \(|x_1 - x_2| + |y_1 - y_2|\)。预先计算所有关键点(起点、中间点、终点)两两之间的距离,存储于
dist[i][j] - 状态压缩 DP(TSP 型):\(\text{dp}[\text{mask}][u]\) 表示已经访问过 \(\text{mask}\) 所代表的中间点集合(\(M\) 位二进制,第 \(i\) 位为 \(1\) 表示 \(P_i\) 已访问),且当前位于点 \(u\)(\(0\) 表示起点,\(1..M\) 表示中间点)的最小移动距离
- 终点单独处理:终点 \(T\) 不纳入状态压缩(无需"访问"),而是作为最终落脚点。在 DP 结束后,枚举访问完所有中间点后的位置 \(i\),加上 \(\text{dist}[i][T]\) 取最小值
- 曼哈顿距离预处理:对于无障碍网格,两点 \((x_1, y_1)\) 和 \((x_2, y_2)\) 的最短距离为 \(|x_1 - x_2| + |y_1 - y_2|\)。预先计算所有关键点(起点、中间点、终点)两两之间的距离,存储于
-
关键步骤:
- 坐标转换:将单元格编号 \(t\)(\(1\)~\(N^2\))转换为网格坐标 \((r, c)\):\(r = \lceil t/N \rceil\),\(c = (t-1) \bmod N + 1\)
- 距离矩阵计算(\(O(M^2)\)):
- 对每对关键点 \((i, j)\),计算曼哈顿距离 \(\text{dist}[i][j] = |x_i - x_j| + |y_i - y_j|\)
- 状态压缩 DP(\(O(2^M \cdot M^2)\)):
- 初始化:\(\text{dp}[0][0] = 0\)(从起点出发,未访问任何中间点)
- 枚举所有状态掩码 \(\text{mask} \in [0, 2^M)\)
- 枚举当前所在点 \(u \in [0, M]\)
- 若当前状态不可达则跳过
- 枚举下一个未访问的中间点 \(v \in [1, M]\):
- 若 \(\text{mask}\) 的第 \(v-1\) 位已为 \(1\),跳过
- 新状态:\(\text{new\_mask} = \text{mask} \mid (1 << (v-1))\)
- 状态转移:\(\text{dp}[\text{new\_mask}][v] = \min(\text{dp}[\text{new\_mask}][v], \text{dp}[\text{mask}][u] + \text{dist}[u][v])\)
- 统计答案:
- \(\text{ans} = \min_{i=0}^{M} \{\text{dp}[(1<<M)-1][i] + \text{dist}[i][T]\}\)
- 即从访问完所有中间点后的任意位置 \(i\),走到终点 \(T\) 的最短总距离
-
时间/空间复杂度:
- 时间复杂度:\(O(M^2 + 2^M \cdot M^2)\)。预处理距离矩阵 \(O(M^2)\),状压 DP 有 \(2^M \cdot (M+1)\) 个状态,每个状态转移 \(O(M)\)
- 空间复杂度:\(O(M^2 + 2^M \cdot M)\),存储距离矩阵和 DP 数组
-
状态压缩 TSP 的核心思想:
- 从指数级枚举到状态压缩:TSP 的暴力全排列复杂度为 \(O(M!)\),无法接受。状压 DP 用 \(M\) 位二进制数表示已访问的点的集合,将状态数降到 \(O(2^M)\),再配合当前位置维度,总复杂度 \(O(2^M \cdot M^2)\)
- 子集状态的递推:\(\text{dp}[\text{mask}][u]\) 的值只依赖于比 \(\text{mask}\) 少一个位的子集,因此可以按掩码从小到大递推,保证计算时所需子状态已求出
- 曼哈顿距离的利用:本题网格无障碍,因此不需要 BFS/最短路算法求两点距离,直接 \(O(1)\) 计算曼哈顿距离即可,大幅简化了问题
- 终点不纳入状态的设计技巧:终点只需要"到达"而不需要"访问",因此不将其放入状态掩码,而是在最后统一加上到终点的距离。这种设计减少了状态维度
- 适用于:经过所有指定点的最短路径、TSP 及其变体、带顺序约束的多点遍历问题
【解题思路】

【算法标签】
状压DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 20, INF = 1e9;
#define int long long
typedef pair<int, int> PII;
int n, m, s, t; // n: 网格大小,m: 中间点数,s: 起点,t: 终点
PII p[N]; // 存储所有点(包括起点、中间点、终点)的坐标
int dp[1 << N][N]; // dp[mask][u]: 访问过mask中的点,当前在点u的最小距离
int dist[N][N]; // 点之间的距离
// 将编号t(1~n*n)转换为网格坐标(x, y),1-based
PII getpos(int t)
{
int x = (t + n - 1) / n; // 行号
int y = t % n; // 列号
if (y == 0) // 如果列号为0,表示是最后一列
{
y = n;
}
return {x, y};
}
// 计算点x和点y之间的曼哈顿距离
int getdist(int x, int y)
{
return abs(p[x].first - p[y].first) + abs(p[x].second - p[y].second);
}
signed main()
{
cin >> n >> m >> s >> t; // 读入网格大小、中间点数、起点、终点
p[0] = getpos(s); // 起点是第0个点
for (int i = 1; i <= m; i++) // 读入中间点
{
int x;
cin >> x;
p[i] = getpos(x);
}
p[++m] = getpos(t); // 终点是第m个点
// 计算所有点对之间的距离
for (int i = 0; i <= m; i++)
{
for (int j = 0; j <= m; j++)
{
dist[i][j] = getdist(i, j);
}
}
memset(dp, 0x3f, sizeof(dp)); // 初始化DP数组为极大值
dp[0][0] = 0; // 初始状态:从起点(点0)开始
// 状态压缩DP
for (int mask = 0; mask < (1 << m); mask++) // 遍历所有状态掩码
{
for (int u = 0; u <= m; u++) // 遍历当前所在点
{
if (dp[mask][u] == 0x3f3f3f3f) // 当前状态不可达
{
continue;
}
for (int v = 1; v <= m; v++) // 尝试前往下一个点v
{
if (mask & (1 << (v - 1))) // 如果点v已经被访问过
{
continue;
}
int new_mask = mask | (1 << (v - 1)); // 新状态掩码
int new_value = dp[mask][u] + dist[u][v]; // 新距离
if (new_value < dp[new_mask][v]) // 如果新距离更小
{
dp[new_mask][v] = new_value; // 更新DP值
}
}
}
}
int ans = INF;
for (int i = 0; i <= m; i++) // 遍历所有点作为访问完所有中间点后的位置
{
// dp[(1<<m)-1][i]: 访问完所有m个中间点,当前在点i的最小距离
// dist[i][m]: 从点i到终点的距离
ans = min(ans, dp[(1 << m) - 1][i] + dist[i][m]);
}
cout << ans << endl; // 输出最短总距离
return 0;
}
【运行结果】
3 2
1 9
5 3
6
浙公网安备 33010602011771号