思路:基本状压dp
看题目知道此题求的是最短哈密顿路径,也就是一条从1到n的经过所有点的最短路径。
我们可以使用状态压缩,使用一个二进制数state代表一种状态,state代表经过的所有点的集合。
例如
state=1,代表只经过1号点。
state=3(二进制为0011),代表经过1号点和2号点。
state=5(二进制为0101),代表经过1号点和3号点。
state=6(二进制为0110),代表经过2号点和3号点。
.......
我们设定dp[state][j]代表经过点的集合为state,并且最后一个点为j号点。
由此推出状态转移方程为:dp[state][j] = min(dp[state - (1 <<j-1) ][k] + w[k][j] )
state - (1 <<j-1),表示从state点集中删去第j号点,w[k][j]代表从k到j路径的长度。
代表的含义为:依次遍历终点为j的每条边,以及对应的点集,从而可以推出dp[state][j]的最小值。
核心代码1:这是写的第一版代码,有个小问题
//init memset(dp, 0x3f, sizeof dp); for (int i = 1; i <= N; i++) { //只有一个点,所以路径为0 dp[1<<(i-1)][i] = 0; } // for (int state = 1; state <= (1<<N) - 1; state++) { // 枚举所有状态 for (int j = 1; j <= N; j++) { // 1 or 2 if(state & 1 << (j-1) && state - (1 << (j-1))){ //判断状态state是否包含第j个点,并且状态中最少有两个点。 int minj = INT_MAX; for (int k = 1; k <= N; k++) { minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]); //状态转移方程 } dp[state][j] = minj; } } }
在上面的代码中的初始化部分,首先将所有状态初始化为INF,因为要求的是最小值,所有初始化为最大值,避免产生干扰。
然后将所有只有一个点的状态初始化为0.
接下来就是状态转移方程,求最小值。
但是,这里有问题,在遍历 j 的时候,我的 j 是从第一个点开始遍历的,这将导致求出来的最短路径可能不是从第一个点开始的,这样只能算出以最后一个点结尾的最短路径,而不能算出从第一个点开始,以最后一个点结尾的最短路径。
例如:
样例1:
0 10 20 999
5 0 90 30
99 50 0 10
999 1 2 0
最后求出的结果为:2 -> 1 -> 3 -> 4,明显看到不是以1号点开始。
核心代码2:改进后
//init memset(dp, 0x3f, sizeof dp); for (int i = 1; i <= N; i++) { //只有一个点,所以路径为0 dp[1<<(i-1)][i] = 0; } // for (int state = 1; state <= (1<<N) - 1; state++) { // 枚举所有状态 for (int j = 2; j <= N; j++) { // 1 or 2 if(state & 1 << (j-1) && state - (1 << (j-1))){ //判断状态state是否包含第j个点,并且状态中最少有两个点。 int minj = INT_MAX; for (int k = 1; k <= N; k++) { minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]); //状态转移方程 } dp[state][j] = minj; } } }
改进之处为: j 不再从1号点开始,而从2号点开始,如此一来,相当于所有1号点的入边都被忽略了,只计算了1号点的出边。这样肯定只能从1号点开始。
最后附上AC代码:
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <cmath> #include <limits.h> #include <iomanip> #include <queue> #include <cstring> using namespace std; typedef long long LL; typedef vector<int> vec; //#pragma GCC optimize(2) static int N; static int G[20][20]; static int dp[1<<20][20]; //dp[state][j] 经过的点集为state,最后一个点为j void work(void){ //init memset(dp, 0x3f, sizeof dp); for (int i = 1; i <= N; i++) { //只有一个点,所以路径为0 dp[1<<(i-1)][i] = 0; } // for (int state = 1; state <= (1<<N) - 1; state++) { // 枚举所有状态 for (int j = 2; j <= N; j++) { // 1 or 2 if(state & 1 << (j-1) && state - (1 << (j-1))){ //判断状态state是否包含第j个点 int minj = INT_MAX; for (int k = 1; k <= N; k++) { minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]); } dp[state][j] = minj; } } } cout << dp[(1<<N)-1][N] << endl; } int main() { // freopen("E:\\Desktop\\data.txt", "r", stdin); //ios::sync_with_stdio(false); cin >> N; for (int i = 1; i <= N; i++) { for (int j = 1; j <= N; j++) { cin >> G[i][j]; } } work(); return 0; }