状压dp

P10447 最短 Hamilton 路径

https://www.luogu.com.cn/problem/P10447


Description

给定一张 n 个点的带权无向完全图,求出从 0 到 n−1 经过每个点恰好一次的最短路径。

Solution

Brute Force

考虑最朴素的做法。

由于每个点只能经过一次,所以可以枚举 n 个点的全排列。对于每个全排列,遍历每个点,计算路径长度。最后找到最短的路径长度即可。

时间复杂度:O(n⋅n!),20! 铁定是会 TLE 的。


DP

n≤20 的数据范围,除了搜索,指数级别的算法也是可以的。可以考虑状态压缩 DP

状态表示

首先,我们要在任何时刻都可以表示已经经过了哪些点。可以使用一个 n 位二进制数 S,第 i(0≤i<n) 位为 1 则表示这个点已经经过,为 0 则表示这个点尚未经过。

仅仅这样一个二进制数够了吗?假如已经知道通过的点集为 S,枚举了下一个点,但是无法得知是从哪个点过来的。

所以,我们还需要记录最后一个经过的点 v。

综上所述,状态标识为 f(S,v),表示已经通过了 S 中为 1 的点,且最后一个点(当前所在的点)为 v。

状态转移

显然,最外层循环要枚举 S,然后第二层循环枚举 v。然后需要进行一个特判。v 是已经经过了的,但是如果 S 中的第 v 位为 0(即表示还没有经过),这是不合逻辑的,需要跳过。

现在,我们想要得到 f(S,v) 的值。v 是最后经过的点,那么我们需要知道,v 是从哪个点过来的。所以可以在 S 内枚举点 u,表示 u→v。当然,可以特判 u=v,但是本题中保证 u→u 的边权为 0,所以可以不用判断。

接下来,f(S,v)←f(S−{v},u)+g(u,v)(g(x,y) 表示 x→y 的边权),意义即为从没有经过 v 的状态且最后一个点为 u,然后到达 v,此间路径长度增加了 g(u,v)。由于 v∈S,所以 S−{v} 可以写成 S-(1<<v)


初始化

开始时 f 赋值为 +∞,但是 f(1,0)←0。因为初始在 0 点,S(2)​=000⋯01(2)​=1(10)​,v=0,此时的路径长度为 0。


结果

要通过每个点恰好一次并且最后到达点 n−1,所以 S=(1 << n) – 1  ,也就是n个1


#include<bits/stdc++.h>

using namespace std;
const int N=20;
int n, g[N][N], f[1<<N][N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin>>g[i][j];
    memset(f, 0x3f, sizeof f);
#if 1
    f[1][0]=0;
    for(int i=1;i<(1<<n);i++){
        for(int u=0;u<n;u++){
            if(i&(1<<u)){// u在里面
                for(int v=0;v<n;v++){
                    if(i&(1<<v))// v在里面
                        f[i][v]=min(f[i][v], f[i-(1<<v)][u] + g[u][v]);
                }
            }
        }
    }
    cout<<f[(1<<n)-1][n-1]<<endl;
#else
    f[1][0] = 0; // 初始化
    for (int S = 3; S < 1 << n; S += 2) // 加速优化
      for (int v = 1; v < n; ++v) if (S >> v & 1) // v ∈ S 时转移
        for (int u = 0; u < n; ++u)
          if (S >> u & 1) f[S][v] = min(f[S][v], f[S - (1 << v)][u] + g[u][v]); // u ∈ S 时转移
    std::cout << f[(1 << n) - 1][n - 1] << std::endl; // 最终结果。(1<<n)-1 就是 n 个 1 的二进制数
#endif
    return 0;
}


时空复杂度:
  • 时间复杂度:O(n^2⋅2^n);
  • 空间复杂度:O(n⋅2^n)。
posted @ 2025-04-11 16:50  katago  阅读(20)  评论(0)    收藏  举报