最短Hamilton路径
这是一个经典的状压dp的题。
给定一张 𝑛 个点的带权无向图,点从 0∼𝑛−1 标号,求起点 0 到终点 𝑛−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 𝑛−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 𝑛。
接下来 𝑛 行每行 𝑛 个整数,其中第 𝑖 行第 𝑗 个整数表示点 𝑖 到 𝑗 的距离(记为 𝑎[𝑖,𝑗])。
对于任意的𝑥,𝑦,𝑧,数据保证𝑎[𝑥,𝑥]=0,𝑎[𝑥,𝑦]=𝑎[𝑦,𝑥] 并且 𝑎[𝑥,𝑦]+𝑎[𝑦,𝑧]≥𝑎[𝑥,𝑧]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤𝑛≤20
0≤𝑎[𝑖,𝑗]≤10^7
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
代码如下:
//哪些点被用过
//目前停在哪个点上
//步骤
//确定dp数组的含义
//dp数组初始化
//dp数组如何遍历
//状态转移方程
//打印dp数组
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20,M=1<<N;// <<表示左移,1向左移1位 -> 10
int dp[M][N],w[N][N];//w表示的是无权图
//dp[state][j],状态用二进制数表示,每个二进制位对应一个城市(或点)。
//当某个城市被访问过时,其对应的二进制位被设置为1,否则为0。
//dp[i][j]表示状态是i的情况下,到达点 j 的最短路径长度
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>w[i][j];
memset(dp,0x3f,sizeof dp);//因为要求最小值,所以初始化为无穷大
dp[1][0]=0;//因为零是起点,所以dp[1][0]=0;
for(int i=0;i<1<<n;i++)//i表示所有的情况
for(int j=0;j<n;j++)//j表示走到哪一个点
if(i>>j&1)//判断状态i的第j位是否为1
for(int k=0;k<n;k++)//k表示走到j这个点之前,以k为终点的最短距离
if(i>>k&1)//判断状态i的第k位是否为1
//在二进制表示中,位的编号通常是从右往左数的,也就是从最低位开始数,从第0位开始
dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]);//更新最短距离
cout<<dp[(1<<n)-1][n-1]<<endl;//表示所有点都走过了,且终点是n-1的最短距离
//位运算的优先级低于'+'-'所以有必要的情况下要打括号
//(1<<n)-1表示所有的城市都被访问过了。假设n=3,1<<3为1000,再减1的话为111,1表示被访问过。
//这样操作以后我们就能得到全部都被访问过了,而且题目说的是0-n-1,所以(1<<n)-1表示0-n-1都被访问了
return 0;
}
//状态转移方程
//dp[i-(1<<j)][k]:这表示在状态i中去掉了点j(即将状态i的第j位设置为0)后到达点k的最短路径长度。
//i-(1<<j)这会将状态i的第j位设置为0,而保持其他位不变。
//换句话说,它表示在还没有访问点j之前的某个时刻,到达点k的最短路径。
//w[k][j]:这表示从点k到点j的直接距离(或权重)。
变式:毕业旅行问题
小明目前在做一份毕业旅行的规划。
打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。
由于经费有限,小明希望能够通过合理的路线安排尽可能的省些路上的花销。
给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。
注意:北京为 1 号城市。
输入格式
第一行包含一个正整数 𝑛,表示城市个数。
接下来输入一个 𝑛 行 𝑛 列的矩阵,表示城市间的车票价钱。
输出格式
输出一个整数,表示最小车费花销。
数据范围
1<𝑛≤20,包括北京
车票价格均不超过 1000元。
输入样例:
4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0
输出样例:
13
说明
共 4 个城市,城市 1和城市 1的车费为 0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,城市 1和城市 4 之间的车费为 5,以此类推。
假设任意两个城市之间均有单程票可买,且价格均在1000 元以内,无需考虑极端情况。
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20, M = 1 << N, INF = 0x3f3f3f3f;
int n;
int w[N][N], f[M][N];
int main()
{
cin>>n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
cin>>w[i][j];
memset(f, 0x3f, sizeof f);
f[1][0] = 0;
for (int i = 0; i < 1 << n; i++)//
for (int j = 0; j < n; j ++ )
if (i >> j & 1)//判断状态i的第j位是否为1
for (int k = 0; k < n; k ++ )
if (i>>k&1)//判断状态i的第k位是否为1
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
//计算从所有城市访问完其他城市后返回起始城市的最小总距离
int res = INF;
for (int i = 1; i < n; i ++ )
res = min(res, f[(1 << n) - 1][i] + w[i][0]);
//f[][]里面包含着很多方案,有可能最后我从1->0,也用可能我从2->0,有很多种方案,但是我只要最小值
//每个城市之间均乘坐高铁,所以最后我直接返回起始城市(北京)就行了。
cout<<res<<endl;
return 0;
}