[狀壓dp] 最短Hamilton路徑(模板題+狀壓dp)

Y_puyu發表於2020-11-11

0. 前言

狀壓 dp 就是採用二進位制數儲存狀態,方便進行位運算操作。例如 八皇后、八數碼問題也都是採用了狀態壓縮的思想來使用一個二進位制數唯一對應集合中的一個狀態。

關鍵是要體會採用二進位制數來表示狀態的思想,要轉變傳統思維,學習接收並吸收這種思想。

1. 狀壓dp 模板題

91. 最短Hamilton路徑

在這裡插入圖片描述
這個問題如果暴力來做的話,首先需要確定走過 0~n-1 這些點的順序,這個就是 n!,然後再求長度,是 n,那麼總共就是 n!*n 時間爆炸…


狀壓 dp 思路:

重點: 狀壓 dp

思路:

  • 狀態定義:
    • f[i][j]:所有從 0 號點走到 j 號點,走過的所有點狀態壓縮為 i 的所有路徑的最小值。
    • 狀壓方式為:因為只有 20 個點,即將 i 看作一個二進位制數,取其前 20 位,每一位 01 代表這個點是否走過
  • 狀態轉移:
    • 分類依據:所有路徑都是從 0 走到 j 的,中間走過的所有點是 i。可以以當前路徑的倒數第二個點進行分類,有:
      • 倒數第二個點是第 0 號點
      • 倒數第二個點是第 1 號點
      • 倒數第二個點是第 2 號點
      • 倒數第二個點是第 n-1 號點
      • 則這樣就能夠不重不漏的將整個狀態劃分完畢
    • 狀態轉移方程f[i][j] = f[i - (1 << j)][k] + w[k][j]
  • 狀態初始化:f[1][0]=0,第 0 個點走向自身的距離為 0。由於取 min,故其它 f[i][j]=inf
  • 返回答案f[(1 << n) - 1][n - 1] 走完 n 個點(即 0~n-1 二進位制數位均為 1),且走到第 n-1 個點的最小值即為答案

本題非常非常非常經典,是狀壓 dp 入門經典題目,不論思路還是程式碼都是很好想的。

程式碼:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 20, M = 1 << N;

int n;
int w[N][N];
int f[M][N];

int main() {
    cin >> n;
    
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            cin >> w[i][j];
            
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    
    for (int i = 0; i < 1 << n; ++i)            // 列舉所有路徑
        for (int j = 0; j < n; ++j)             // 列舉所有點
            if (i >> j & 1)                     // 如果路徑包含該點
                for (int k = 0; k < n; ++k)     // 列舉轉移的點
                    if ((i - (1 << j)) >> k & 1)// 除去j點,一定包含k這個點,才能走通
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
    
    cout << f[(1 << n) - 1][n - 1] << endl;     // 走完n個點,且走到第n-1個點的最小值即為答案
    
    return 0;
}

相關文章