最短Hamilton路径
最短Hamilton路径
https://ac.nowcoder.com/acm/contest/996/D
最短Hamilton路径
题目描述
给定一张 n(n≤20) 个点的带权无向图,点从0∼n−1标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
输入描述
第一行一个整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10710^7的正整数,记为a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且a[x,y]+a[y,z]≥a[x,z]。
输出描述
输出一个整数,表示最短 Hamilton 路径的长度。
输入样例
4 0 2 1 3 2 0 2 1 1 2 0 1 3 1 1 0
输出样例
4
说明
从0到3的Hamilton路径有两条,0-1-2-3和0-2-1-3。前者的长度为2+2+1=5,后者的长度为1+2+1=4
思路
首先题目需要求出最短路径,按照朴素的方法,需要遍历所有的转态,即20个点的所有排列,状态数量为 20!,显然这是无法接受的时间复杂度,所以我们需要另一种策略,即将每次产生的最优解状态保存,使用状态转移,
状态转移方程:step[status][j] = min{step[status - (1 << j)][k] + weight[k][j]}
status是当前已经过的点的集合,若其第i位为1,则表示第i个点已被遍历,否则则未经过,status - (1 << j)表示从经过的所有点中去除点j, weight[k][j]表示从点k到点j的路径长度
所以这个状态转移方程就是找一个中间点 k,将已经走过点的集合 status 中去除掉 j ,然后再加上从 k 到 j 的权值
现在我们有了基本思路,可以开始设计代码了,第一时间,我想到的是记忆搜索,代码写好后发现了一个严重的问题,栈溢出了,由于递归过程中产生了大量的自动变量,栈区大小熬不过堆区的几十M的steps[1 << N][N],所以又改成了使用动态规划,终于把题目给AC了。回顾一下思路历程 暴力搜索 --> 记忆搜索 --> 动态规划, 当时最先想到的怎么会是直接暴搜呢,应该还是还是做(tai)得(cai)少(le)。
下面给出记忆搜索(不能通过)和动态规划的代码。
示例代码
#include <iostream> #include <limits> using namespace std; const int INF = std::numeric_limits<int>::max() / 2; const int N = 21; int weight[N][N]; int steps[1 << N][N]; int n; #define DP #ifdef DP // 使用DP算法 int get_step(int ss, int endpoint) { steps[1][0] = 0; for (int status = 1; status < 1 << n; status++) { for (int endpoint = 0; endpoint < n; endpoint++) if (status & (1 << endpoint)) { for (int j = 0; j < n; j++) if (status & (1 << j)) steps[status][endpoint] = min( steps[status][endpoint], steps[status - (1 << endpoint)][j] + weight[j][endpoint]); } } return steps[ss][endpoint]; } #else // 使用记忆搜索算法 int get_step(int status, int endpoint) { if (steps[status][endpoint] != INF) { return steps[status][endpoint]; } if (status == 1 && endpoint == 0) { return steps[status][endpoint] = 0; } for (int k = 0; k < n; k++) { if (((status - (1 << endpoint)) >> k) & 1) { steps[status][endpoint] = min( steps[status][endpoint], get_step(status - (1 << endpoint), k) + weight[k][endpoint]); } } return steps[status][endpoint]; } #endif int main(int argc, char const* argv[]) { for (auto& ints : steps) for (auto& i : ints) i = INF; std::cin >> n; for (int i = 0; i < n; i++) for (int j = 0; j < n;j++) std::cin >> weight[i][j]; std::cout << get_step((1 << n) - 1, n - 1); return 0; }