二进制状态压缩, 最短Hamilton路径

二进制状态压缩

使用一个长度为 $m$ 的二进制数来表示并存储一个长度为 $m$ 的 $bool$ 数组

通过一系列操作可以实现原 $bool$ 数组中对应下标的存取

操作 运算

取出整数n在二进制表示下的第k位

取出整数n在二进制表示下的第0 ~ k-1位

把整数n在二进制表示下的第k位取反

对整数n在二进制表示下的第k位赋值1

对整数n在二进制表示下的第k位赋值0

n >> k & 1

n & ((1 << k) - 1)

n xor (1 << k)

n | (1 << k)

n & (~(1 << k))

最短Hamilton路径

最短Hamilton路径的定义为从起点0到终点 $n-1$ 不重不漏地经过每个点恰好一次

给定一张 $ n(n \leq 20) $ 个点的带权无向图, 点从0 ~ $n-1$ 编号, 求从起点0到终点 $n-1$ 的最短Hamilton路径

 

暴力求解, 枚举 $n$ 个点的全排列, 时间复杂度为 $O(n \cdot n!)$, 可以使用二进制状态压缩dp来优化到 $ O(n^{2} \cdot 2^{n}) $

状压dp求解

状态表示
使用一个 $n$ 位二进制数来表示哪些点已经被经过, 哪些点还没被经过, 具体为若其第 $i$ 位为1, 则表示经过, 反之未经过
还需要多开一维, 记录当前状态下在处于哪个点
所以状态表示为 $ dp[(1 << N)][N] $, 第一维表示点被经过的状态, 第二维表示当前所处的点

状态计算
在起点处的状态 $ dp[1][0] $ 初始化为0, 其余点求 $min$ 初始化为INF, 最终答案就是求解 $ dp[(1 << n) - 1][n - 1] $
对于每个状态 $dp[i][j]$ (状态为1, 停在 $j$ 点)来说, 可以遍历除当前所处点 $j$ 的其余位置上为1的点, 然后取 $min$
即 $ dp[i][j] = min(dp[i][j], dp[i \; xor \; 1 << j][k] + w[k][j]]) $ (找到这个状态是由哪个状态转移过来的, 即上一步所处点)

保证求解过程满足拓扑序

保证求解每个状态时去掉该位置的1已经被求解过了, 即从小到大求解

void solve() {
    //初始化所有状态的距离为INF, 状态为1目前在0点的距离为0
    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位得是1才行
                for(int k = 0; k < n; k++)      //枚举上一个所处的点
                    if((i ^ 1 << j) >> k & 1)   //去点j点的1状态中第k位也得是1才行
                        dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][k] + w[k][j]);
    
    cout << dp[(1 << n) - 1][n - 1];        //(1 << n) - 1就是所有点都被经过(所有点都是1)
}
posted @ 2020-10-06 15:51  yikanji  阅读(145)  评论(0)    收藏  举报