《算法竞赛进阶指南》0x01 位运算-4.最短Hamilton路径

AcWing 91. 最短Hamilton路径

题目描述

给定一张 n n n 个点的带权无向图,点从 0 ∼ n − 1 0 \sim n-1 0∼n−1 标号,求起点 0 0 0 到终点 n − 1 n-1 n−1 的最短 H a m i l t o n Hamilton Hamilton 路径。

H a m i l t o n Hamilton Hamilton 路径的定义是从 0 0 0 到 n − 1 n-1 n−1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 n n n。

接下来 n n n 行每行 n n n 个整数,其中第 i i i 行第 j j j 个整数表示点 i i i 到 j j j 的距离(记为 a [ i , j ] a[i,j] a[i,j])。

对于任意的 x , y , z x,y,z x,y,z,数据保证 a [ x , x ] = 0 , a [ x , y ] = a [ y , x ] a[x,x]=0,a[x,y]=a[y,x] a[x,x]=0,a[x,y]=a[y,x] 并且 a [ x , y ] + a [ y , z ] ≥ a [ x , z ] a[x,y]+a[y,z] \ge a[x,z] a[x,y]+a[y,z]≥a[x,z]。

输出格式

输出一个整数,表示最短 H a m i l t o n Hamilton Hamilton 路径的长度。

数据范围

1 ≤ n ≤ 20 1 \le n \le 20 1≤n≤20
0 ≤ a [ i , j ] ≤ 10 7 0 \le a[i,j] \le 10^7 0≤a[i,j]≤107

输入样例:
复制代码
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
解题思路

由于 n ≤ 20 n≤20 n≤20,无法用全排列枚举( 20 ! 20! 20! 太大)所有方案,需要使用状态压缩动态规划。

【状态定义】 d p [ s t a t e ] [ i ] dp[state][i] dp[state][i] 表示:当前已经访问过的点集为 s t a t e state state(二进制第 k k k 位为 1 1 1 表示点 k k k 已访问),并且最后停留在点 i i i 时的最短路径长度。

例如: s t a t e = 5 state = 5 state=5(二进制 101 101 101)表示访问了点 0 0 0 和点 2 2 2,最后在某个点。

【初始状态】 只访问了点 0 0 0,且最后在 0 0 0,长度为 0 0 0: d p [ 1 ] [ 0 ] = 0 dp[1][0] = 0 dp[1][0]=0。其余状态初始化为无穷大。

【状态转移】对于当前状态 s t a t e state state,假设最后到达的点是 j j j,那么上一个状态一定是 s t a t e state state 中去掉 j j j(即 s t a t e − ( 1 < < j ) state − (1<<j) state−(1<<j)),且上一个状态最后停留在某个点 k k k( k k k 必须是上一个状态中已访问的点)。

转移方程: d p [ s t a t e ] [ j ] = m i n ( d p [ s t a t e ] [ j ] , d p [ s t a t e − ( 1 < < j ) ] [ k ] + w [ k ] [ j ] ) dp[state][j] = min(dp[state][j], dp[state − (1<<j)][k] + w[k][j]) dp[state][j]=min(dp[state][j],dp[state−(1<<j)][k]+w[k][j])

【最终答案】 所有点都访问过: s t a t e = ( 1 < < n ) − 1 state=(1<<n)−1 state=(1<<n)−1,最后停在终点 n − 1 n−1 n−1,即 d p [ ( 1 < < n ) − 1 ] [ n − 1 ] dp[(1<<n)−1][n−1] dp[(1<<n)−1][n−1]。

参考代码
无注释
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N =20;
const int M = 1 << N;
int n, m[N][N], dp[M][N];

int main(){
    cin >> n;
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++) cin >> m[i][j];
    memset(dp, 0x3f, sizeof dp);
    dp[1][0] = 0;
    for(int i = 2; i < (1 << n); i ++)
        for(int j = 0; j < n; j ++)
            if((i >> j) & 1)
                for(int k = 0; k < n; k ++)
                    if((i >> k) & 1)
                    	if(j != k) 
                        	dp[i][j]=min(dp[i][j], dp[i - (1 << j)][k] + m[k][j]);
    cout << dp[(1 << n) - 1][n - 1];
    return 0;
}
有注释
cpp 复制代码
#include <bits/stdc++.h>  // 包含所有常用头文件

using namespace std;
const int N = 20;      // 点的最大数量,因为n≤20
const int M = 1 << N;  // 状态总数:每个点有经过/未经过两种状态,所以总状态数为2^n
int n;                 // 实际点的个数
int m[N][N];           // 邻接矩阵,存储两点之间的距离
int dp[M][N];  // dp[i][j]表示当前已经经过的点的集合为i,且当前停在点j的最短路径长度

int main() {
    cin >> n;  // 输入点的数量

    // 读入邻接矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) cin >> m[i][j];

    // 初始化dp数组为很大的数(0x3f3f3f3f表示无穷大)
    memset(dp, 0x3f, sizeof dp);

    // 初始状态:只经过了起点0,当前在点0,路径长度为0
    // 二进制数1表示只有第0位是1(即只有点0被经过)
    dp[1][0] = 0;

    // 遍历所有可能的状态集合
    // i表示当前经过的点的集合(用二进制表示,第k位为1表示点k已经过)
    // 从2开始是因为状态1(只有起点)已经初始化,状态0(没有点)没有意义
    for (int i = 2; i < (1 << n); i++)
        // 遍历所有可能的当前所在点j
        for (int j = 0; j < n; j++)
            // 如果点j在当前经过的集合i中(即最后一步可以停在j)
            if ((i >> j) & 1)
                // 遍历所有可能的上一个点k
                for (int k = 0; k < n; k++)
                    // 如果点k也在集合i中(即上一步是从k走到j)
                    if ((i >> k) & 1)
                    	if(j != k) 	//最后一个点和倒数第二个点不相同
                        // 状态转移方程:
                        // 从集合i中去除点j(i - (1 << j)),上一个点是k
                        // 加上从k到j的距离m[k][j]
                        // 取所有可能的上一个点k中的最小值
                        	dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + m[k][j]);

    // 输出最终结果
    // (1 << n) - 1 表示经过所有点的集合(二进制全1)
    // n - 1 表示终点
    // 即经过所有点且停在终点的最短路径长度
    cout << dp[(1 << n) - 1][n - 1];

    return 0;
}
相关推荐
苦藤新鸡1 小时前
65.搜索平移数组的最小值
算法·leetcode
载数而行5201 小时前
算法系列5之交换排序
c语言·数据结构·c++·算法·排序算法
重生之后端学习1 小时前
35. 搜索插入位置
java·数据结构·算法·leetcode·职场和发展·深度优先
楼田莉子1 小时前
C++并发库介绍(上)
开发语言·c++·学习
Trouvaille ~1 小时前
【项目篇】从零手写高并发服务器(一):项目介绍与开发环境搭建
linux·运维·服务器·网络·c++·高并发·muduo库
程序员南飞1 小时前
算法笔试-求一个字符串的所有子串
java·开发语言·数据结构·python·算法·排序算法
秦jh_1 小时前
【C++】哈希扩展
开发语言·c++·哈希算法
陆嵩2 小时前
从一个小例子实践代数多重网格方法
算法·amg·多重网格·粗化·插值算子·光滑·v cycle
elseif1232 小时前
循环队列(详细)GESP六级
数据结构·c++·队列·循环队列