题解:AtCoder AT_awc0017_e Optimization of Delivery Routes
【题目来源】
AtCoder:E - Optimization of Delivery Routes
【题目描述】
Takahashi works as a delivery driver. There are a total of \(N\) locations relevant to today's deliveries, each numbered from \(1\) to \(N\). Location \(1\) is the distribution center (serving as both the starting point and the return point), and locations \(2\) through \(N\) are the destinations where packages must be delivered.
高桥是一名送货司机。今天配送总共涉及 \(N\) 个地点,每个地点编号从 \(1\) 到 \(N\)。地点 \(1\) 是配送中心(既作为起点也作为返回点),地点 \(2\) 到 \(N\) 是需要送达包裹的目的地。
Each location \(i\) is positioned at coordinates \((X_i, Y_i)\) on a two-dimensional plane. Note that different locations may share the same coordinates.
每个地点 \(i\) 位于二维平面上的坐标 \((X_i, Y_i)\)。注意,不同的地点可能共享相同的坐标。
Takahashi plans a tour route starting from the distribution center (location \(1\)), visiting all destinations (locations \(2\) through \(N\)) exactly once each, and then returning to the distribution center (location \(1\)). Specifically, he chooses a permutation \((p_1, p_2, \ldots, p_{N-1})\) of \((2, 3, \ldots, N)\) and travels in the order:
高桥计划一条从配送中心(地点 \(1\))出发的巡回路线,访问所有目的地(地点 \(2\) 到 \(N\))各恰好一次,然后返回配送中心(地点 \(1\))。具体来说,他选择一个 \((2, 3, …, N)\) 的排列 \((p_1, p_2, …, p_{N-1})\),并按以下顺序行进:
\(1 \to p_1 \to p_2 \to \cdots \to p_{N-1} \to 1\)
The fuel cost for Takahashi's delivery truck to travel directly from coordinates \((a, b)\) to coordinates \((c, d)\) is calculated as \((a - c)^2 + (b - d)^2\).
高桥的送货卡车直接从坐标 \((a, b)\) 行驶到坐标 \((c, d)\) 的燃料成本计算公式为 \((a - c)² + (b - d)²\)。
The total fuel cost of the tour route is the sum of the travel costs between each pair of consecutive locations along the route. Specifically, the fuel cost of the above tour route is:
巡回路线的总燃料成本是路线上每一对连续地点之间的行驶成本之和。具体来说,上述巡回路线的燃料成本为:
\(\mathrm{dist}(1, p_1) + \sum_{k=1}^{N-2} \mathrm{dist}(p_k, p_{k+1}) + \mathrm{dist}(p_{N-1}, 1)\)
where \(\mathrm{dist}(i, j) = (X_i - X_j)^2 + (Y_i - Y_j)^2\) represents the travel cost from location \(i\) to location \(j\).
其中 \(\mathrm{dist}(i, j) = (X_i - X_j)^2 + (Y_i - Y_j)^2\) 表示从地点 \(i\) 到地点 \(j\) 的行驶成本。
Takahashi wants to minimize the total fuel cost of the tour route by freely choosing the order in which he visits the destinations. Travel is done directly along the tour route; he cannot pass through other locations or arbitrary coordinates along the way.
高桥希望通过自由选择访问目的地的顺序,来最小化巡回路线的总燃料成本。旅行是沿着巡回路线上直接进行的;他不能途中经过其他地点或任意坐标。
Find the minimum fuel cost.
求最小燃料成本。
【输入】
\(N\)
\(X_1\) \(Y_1\)
\(X_2\) \(Y_2\)
\(\vdots\)
\(X_N\) \(Y_N\)
- The first line contains an integer \(N\) representing the number of locations.
- Lines \(2\) through \(N + 1\) give the coordinates of each location.
- Line \(1 + i\) contains the \(x\)-coordinate \(X_i\) and the \(y\)-coordinate \(Y_i\) of location \(i\), separated by a space.
【输出】
Output the minimum total fuel cost of the tour route as an integer on a single line.
【输入样例】
2
0 0
1 1
【输出样例】
4
【核心思想】
-
问题分析:给定平面上 \(N\) 个地点的坐标,地点 \(1\) 为配送中心(起点和终点),地点 \(2\) 到 \(N\) 为需要配送的目的地。求一条从地点 \(1\) 出发,经过所有目的地各恰好一次,最后返回地点 \(1\) 的巡回路线,使得总燃料成本最小。两点间成本为欧几里得距离的平方。这是一个经典的旅行商问题(TSP, Traveling Salesman Problem),即求经过所有点并回到起点的最短环路。由于 \(N \leq 20\),全排列 \(O(N!)\) 不可行,但 \(2^N\) 级别的状态压缩动态规划完全可接受。
-
算法选择:
- 状态压缩 DP(TSP 标准解法):\(\text{dp}[\text{mask}][u]\) 表示已经访问过 \(\text{mask}\) 所代表的点集(\(N\) 位二进制,第 \(i\) 位为 \(1\) 表示地点 \(i+1\) 已访问),且当前位于地点 \(u\) 的最小花费
- 距离平方预处理:预先计算所有点对 \((i, j)\) 之间的燃料成本 \(\text{dist}[i][j] = (X_i - X_j)^2 + (Y_i - Y_j)^2\),避免 DP 过程中重复计算
- 起点固定与终点统一:由于必须从地点 \(1\) 出发并最终回到地点 \(1\),将地点 \(1\) 的访问状态(\(\text{mask}\) 的第 \(0\) 位)置为 \(1\) 并作为初始状态,最终答案枚举最后一个访问点 \(i\) 加上返回成本 \(\text{dist}[i][1]\)
-
关键步骤:
- 读取输入与坐标存储:地点数 \(N\) 和每个地点的坐标 \((X_i, Y_i)\)
- 距离矩阵计算(\(O(N^2)\)):
- 对每对地点 \((i, j)\),计算 \(\text{dist}[i][j] = (X_i - X_j)^2 + (Y_i - Y_j)^2\)
- 状态压缩 DP(\(O(2^N \cdot N^2)\)):
- 初始化:\(\text{dp}[1][1] = 0\)(只访问了地点 \(1\),当前在地点 \(1\),花费为 \(0\))。其余状态设为 \(\infty\)
- 枚举所有状态掩码 \(\text{mask} \in [1, 2^N)\)
- 枚举当前所在地点 \(u \in [1, N]\),若当前状态不可达则跳过
- 枚举下一个未访问的地点 \(v \in [1, N]\):
- 若 \(\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=1}^{N} \{\text{dp}[(1<<N)-1][i] + \text{dist}[i][1]\}\)
- 即所有地点都被访问过后,从最后地点 \(i\) 返回配送中心 \(1\) 的最小总成本
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2 + 2^N \cdot N^2)\)。预处理距离矩阵 \(O(N^2)\),状压 DP 有 \(2^N \cdot N\) 个状态,每个状态转移 \(O(N)\)
- 空间复杂度:\(O(N^2 + 2^N \cdot N)\),存储距离矩阵和 DP 数组
-
状态压缩 TSP 的核心思想:
- 子集枚举替代全排列:TSP 暴力枚举所有 \(N!\) 条路径不可行。状压 DP 用二进制数表示已访问点的集合,状态数降至 \(O(2^N)\),总复杂度 \(O(2^N \cdot N^2)\),对于 \(N \leq 20\) 可在合理时间内求解
- 最优子结构:\(\text{dp}[\text{mask}][u]\) 表示访问 \(\text{mask}\) 中的点并以 \(u\) 结尾的最优解。该最优解只依赖于少一个点的子集,因此可以按掩码大小递推
- 哈密顿回路转化:本题要求经过所有点的最短回路。通过固定起点(地点 \(1\)),将回路问题转化为路径问题:从起点出发经过所有点再回到起点。最终答案只需枚举最后一点并加上返回边
- 距离预处理的常数优化:由于坐标距离在 DP 中会被反复使用,\(O(N^2)\) 预处理距离矩阵避免了 DP 转移时的重复计算,显著降低了常数
- 适用于:经过所有点的最短路径/回路、TSP 及变体、配送路线优化、巡回检查等
【解题思路】

【算法标签】
状压DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 20, INF = 1e9;
int n, x[N], y[N]; // n: 点数,x[], y[]: 点的坐标
int dp[1 << N][N]; // dp[mask][u]: 访问过mask中的点,当前在点u的最小花费
int dist[N][N]; // dist[i][j]: 点i到点j的距离平方
int main()
{
cin >> n; // 读入点数
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i]; // 读入每个点的坐标
}
// 计算所有点对之间的距离平方
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
dist[i][j] = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
}
}
memset(dp, 0x3f, sizeof(dp)); // 初始化DP数组为极大值
dp[1][1] = 0; // 初始状态:从点1开始,只访问了点1,花费为0
// 状态压缩DP
for (int mask = 1; mask < (1 << n); mask++) // 遍历所有状态掩码
{
for (int u = 1; u <= n; u++) // 遍历当前所在点
{
if (dp[mask][u] == 0x3f3f3f3f) // 当前状态不可达
{
continue;
}
for (int v = 1; v <= n; v++) // 尝试前往下一个点v
{
if (mask & (1 << (v - 1))) // 如果点v已经被访问过
{
continue;
}
int new_mask = mask | (1 << (v - 1)); // 新状态掩码
int new_cost = dp[mask][u] + dist[u][v]; // 新花费
if (new_cost < dp[new_mask][v]) // 如果新花费更小
{
dp[new_mask][v] = new_cost; // 更新DP值
}
}
}
}
int ans = INF;
for (int i = 1; i <= n; i++) // 遍历所有点作为终点
{
if (dp[(1 << n) - 1][i] != INF) // 如果所有点都被访问过
{
int tot = dp[(1 << n) - 1][i] + dist[i][1]; // 回到起点1的花费
ans = min(ans, tot); // 更新最小花费
}
}
cout << ans << endl; // 输出结果
return 0;
}
【运行结果】
2
0 0
1 1
4
浙公网安备 33010602011771号