AcWing 91. 最短Hamilton路径 状态压缩 位运算

本文详细介绍了如何利用位运算和动态规划解决AcWing上的91题——最短Hamilton路径。由于问题属于NP问题,无法找到多项式时间解法,因此通过暴力枚举并压缩状态来降低时间复杂度。通过设置二维状态数组,根据状态转移方程更新最小边权和,并利用整型数的20位来表示20个点的访问状态。初始状态为只有起点0被访问,边权为0。最后分析了时间复杂度,指出该方法的时间复杂度远小于暴力解法的n!。
摘要由CSDN通过智能技术生成

AcWing 91. 最短Hamilton路径

以下皆参考自y总的视频讲解

1. 问题定义

最短Hamilton路径:
在 n 个点的带权无向图中,起点为0,走完所有点的最小边权和。
这是一个NP问题,即时间复杂度为多项式时间的非确定问题。
简单来说就是没有一个满足多项式时间的解法。

什么是多项式时间:机器上最小的时间复杂度级别,与之对应的是超多项式时间。
什么是超多项式时间:解题时间会大大超过任何多项式时间的问题,比如指数级别的时间复杂度问题。

2. 问题分析

由于没有满足多项式时间解法,这个问题只能采用暴力解法
暴力枚举的时间复杂度是 n!
本题数据 n <= 20 也就是最高 20! 大概是2 * 10^18
本题时间限制为5s 1秒按照常规计算速度 10^8 来算 最高是5 * 10^8
明显暴力解法不可取

对问题进行梳理
假设有4个点 0 1 2 3
0这个点是固定的
所以有 3! = 6 种路径

0->1->2->3 0->1->3->2
0->2->1->3 0->2->3->1
0->3->1->2 0->3->2->1

因为在暴力dfs过程中,我们只关心两个状态

  1. 已经访问过哪些点
  2. 目前停留在哪个点上

考虑设置一个二维状态数组 分别表示两个状态
定义dp[state_i][i]表示当遍历到i这个点时的最小边权和
其中state_i中表示访问过的点(包括 i 本身这个点)

那要如何得到当前的最小边权和?
肯定是除了目前这个点 所有已经访问过的点的最小边权值 加上当前边权
就是我们要的当前最小边权值 也就是用已有值(旧状态)来更新当前值(新状态)

可以得到状态转移方程为

dp[state_i][i] = dp[state_k][k] + weight[k][i]

其中dp[state_k][k]表示除了i这个点
所有访问过的点的最小边权值weight[k][i]ki的边权值

3. 状态表示与压缩

到这里还剩下一个问题没有解决:
如何用一个数表示访问过的点state_i ?

既然一个点只有两种状态,访问或者未访问
可以借助只有两种状态的二进制 (0,1)

计算机一个整型数有4个字节32位 题目最大20个点
也就是可以用一个整型数里面的20位来表示20个点的状态
来达到压缩状态总大小的目的
其中0表示未访问 1表示已访问

要查看 x 这个点的状态 也就是取二进制中第 x 位的值
可以这样做

state_i >> x & 1

表示一个数向左位移x位之后,此时第x位在最低位,然后&1取最低位

比如当前state_i = 6(十进制) = 0110(二进制)
要查看第 2 这个点 也就是

6 >> 2 = 0110 >> 2 = 01

取最低位

01 & 1 = 1

表示 2 这个点已经被访问过了

4. 初始情况

开始的时候 只有0这个点被访问 且边权为0
二进制第0位设置为1 也就是十进制的1

不要忘记先给状态数组赋初值~
题目求最小值 所以初值设置为较大数

5. 时间复杂度

最后分析一下该方法的时间复杂度

<
哈密顿回路是一种经过图中每个节点一次且仅一次的回路。哈密顿回路问题是一个NP完全问题,因此没有已知的多项式时间算法可以解决这个问题。不过,可以使用启发式算法来解决近似的问题。 下面是一个使用Java实现的近似算法: ```java import java.util.*; public class HamiltonianPath { private static int[][] graph; // 图 private static int[] path; // 存储路径 private static boolean[] visited; // 标记是否访问过 private static int n; // 节点数 public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); graph = new int[n][n]; path = new int[n]; visited = new boolean[n]; // 构建图 for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { graph[i][j] = sc.nextInt(); } } // 从第一个节点出发 path[0] = 0; visited[0] = true; if(findHamiltonianPath(1)) { // 打印路径 for(int i = 0; i < n; i++) { System.out.print(path[i] + " "); } } else { System.out.println("No Hamiltonian Path exists"); } } // 查找哈密顿路径 private static boolean findHamiltonianPath(int pos) { // 如果已经遍历完所有节点 if(pos == n) { // 判断最后一个节点是否与第一个节点相邻 if(graph[path[pos - 1]][path[0]] == 1) { return true; } else { return false; } } // 遍历其它节点 for(int i = 1; i < n; i++) { if(isValid(i, pos)) { path[pos] = i; visited[i] = true; if(findHamiltonianPath(pos + 1)) { return true; } // 回溯 visited[i] = false; } } return false; } // 判断节点是否可达 private static boolean isValid(int node, int pos) { // 如果节点已经被访问过,返回false if(visited[node]) { return false; } // 如果前一个节点与当前节点不相邻,返回false if(graph[path[pos - 1]][node] == 0) { return false; } return true; } } ``` 在这个算法中,我们使用了回溯的方法来查找哈密顿路径。我们从第一个节点开始,依次尝试访问其它节点,直到找到一条哈密顿路径或者遍历完所有节点。在查找过程中,我们使用visited数组来标记节点是否已经被访问过,使用path数组来存储路径。isValid方法用来判断节点是否可达。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值