狀態壓縮DP與TSP問題
阿新 • • 發佈:2019-01-07
狀態壓縮DP
DP過程中的狀態不可能像揹包問題一樣只有整數,肯定有各種各樣稀奇古怪的狀態,需要不止一個變數來表示。這種情況下如果需要使用DP 就必須把狀態壓縮成一個數來表示,並且一個數只能對應於一種狀態。
特別地,對於集合我們可以把每一個元素的選取與否對應到一個二進位制位裡,從而把狀態壓縮成一個整數,大大方便了計算和維護。
對於不是整數的情況,很多時候很難確定一個合適的遞推順序,因此使用記憶化搜尋可以避免這個問題。如下面TSP問題的法一。
TSP問題
一張圖上有n個點,給定相應的鄰接矩陣,需要求出從0號節點出發,經過且只經過每個頂點一次,最後仍回到0號節點的最小邊權。TSP問題可以用狀壓DP來快速求解。
定義dp[S][v]為已經經過了點集S之後,目前在點v(v已經包含在S中),回到0節點的最小邊權。
所以有如下的遞推公式
**dp[V[0] = 0
dp[S][v] = min(dp[S∪{u}][u]+d[v][u])(其中u不屬於S)**
將S看作一個長度為n的bit流,第幾號節點訪問過就把S的第幾號節點置為1,其他都是0,這樣就可以將狀態壓縮成了一個數字來表示,並且有一一對應性。
採用記憶化搜尋的TSP狀壓DP程式碼如下
int n;
int d[maxn][maxn];
int dp[1<<maxn][maxn];
int rec(int S,int v)
{
if(dp[S][v] >= 0) {
return dp[S][v];
}
if(v == 0 && S == (1<<maxn)-1) return dp[S][v] = 0;
int ans = INF;
for(int i = 0 ; i < n ; i ++) {
if(!(S>>i&1)) {
ans = min(ans,d[v][i]+rec(S|(1<<i),i));
}
}
return dp[S][v] = ans;
}
void solve(void)
{
memset(dp,-1,sizoef(dp));
cout << rec(0,0) << endl;
}
此外也可以不用記憶化搜尋,觀察遞推的順序從而使用迴圈求解。
發現對於任意的兩個整數i和j 如果它們對應的集合滿足S(i)包含於S(j) 那麼i<=j。程式碼如下
int dp[1<<maxn][maxn];
void solve()
{
for(int i = 0 ; i < (1<<n) ; i ++) {
fill(dp[S],dp[S]+n,INF);
}
dp[(1<<n)-1][0] = 0;
for(int S = (1<<n)-1 ; S >= 0 ; S --) {
for(int i = 0 ; i < n ; i ++) {
for(int j = 0 ; j < n ; j ++) {
if(!(S>>j&1))
dp[S][i] = min(dp[S][i],dp[S|1<<j][j]+d[i][j]);
}
}
}
cout << dp[0][0] << endl;
}