acwing-91 最短Hamilton路径 (状压dp) 入门基础

题目链接

题意

给定一张 n 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

题解

这道题如果用一般解法暴力搜索(依次找点,枚举所有路径)的话,我们计算一下复杂度:
每个点都要经过一次,遍历出所有情况的时间复杂度为O(N!);
题目里给出的N是最大是20,算出时间是2432902008176640000,这样必然会T,我们就需要一些优化来减少运算的复杂度;
因为当前的复杂度远远大于给定范围, 如果在原有的搜索方法上剪枝必然是行不通的,需要合并子状态
如果分析一下每条路径和它之前的子路径,我们可以发现我们并不关心他之前的路径具体是什么样的顺序,我们关心的是之前的路径终点在那,下一个可以选那个点;
这里用一个六个点的图来演示一下,我们用最终状态即五个点都用到了来往回推
根据题意需要从0开始,最后停在n-1,中间的状态当前是不确定的所以可以表示为下图:

0
1,2,3,4
5

当前状态我们可以从下面四种子状态来推得

0
1,2,3
4
5
0
1,2,4
3
5
0
1,3,4
2
5
0
2,3,4
1
5

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20, M = 1 << 20;
int dp[M][N], w[N][N];
int n;
int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            scanf("%d", &w[i][j]);
        }
    }
    memset(dp, 0x3f, sizeof dp);
    dp[1][0] = 0;
    for (int i = 0; i < (1 << n); i++)				//枚举状态
    {
        for (int j = 0; j < n; j++)                	//枚举终点
        {
            if (i >> j & 1)                         //判断当前状态是否存在j点
            {
                for (int k = 0; k < n; k++)         //枚举中间点
                {
                    if (i ^ (1 << j) >> k & 1)      //1.删除状态中j点 2.判断状态中是否存在k点
                    {
                        dp[i][j] = min(dp[i][j], dp[i ^ (1 << j)][k] + w[k][j]);
                        //比较当前 以j为终点的状态i的路长 和 以k为终点的状态i^(1<<j)(i状态去掉j)的路长加上k到j的路长,更新以j为终点状态i的路长;
                    }
                }
            }
        }
    }
    printf("%d\n", dp[(1 << n) - 1][n - 1]);
    return 0;
}

发布了19 篇原创文章 · 获赞 19 · 访问量 689

猜你喜欢

转载自blog.csdn.net/qq_44086097/article/details/104146600