1. 程式人生 > >TSP(旅行商問題)-狀壓dp

TSP(旅行商問題)-狀壓dp

給定n個點(0...n - 1)的帶權有向圖,出從0出發經過每點恰好一次再回到的0,求所經過的邊的總權值的最小值

n<=15,d(i, j) < 1000

方程為:dp[S][v] = min(dp[S + u][u], map[v][u])

dp[S][v]指的是已經經過S集合中的點(包括v),從v出發回到0所需要的最小權值;

因為上述的dp方程中,第一個下標是集合不是整數,不好直接做,那怎麼辦呢,

1:把各個S所能取到的值編碼成整數,最直觀的我們想到每個點都只有取和不取兩種狀態,所以可以想到用二進位制編碼(下面程式碼就是這種方法)

2:直接用STL的set模擬集合

樣例:

5 8
4 0 7
4 1 6
3 4 3
0 3 4
2 0 4
0 1 3
2 3 5
1 2 5

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#include<cstring>
#define maxn 16
#define inf  1000000

using namespace std;

int n, m;
int Map[maxn][maxn];
int dp[1 << maxn][maxn];
int Next[maxn];

void pre()
{
    fill(Map[0], Map[0] + maxn * maxn, inf);
    memset(dp, -1, sizeof(dp));
    memset(Next, -1, sizeof(Next));
}

int  solve(int s, int v)
{
    if(dp[s][v] != -1)
        return dp[s][v];

    if(s == (1 << n) - 1 && v == 0)
        return dp[s][v] = 0;

    int Min = inf;
    for(int i = 0; i < n; i++)
    {
           if(!(1 & s >>i) && Min > solve(s | 1 << i, i) + Map[v][i])
           {
               Min = solve(s | 1 << i, i) + Map[v][i];
               Next[v] = i;
           }
    }
    return dp[s][v] = Min;
}

int main()
{
    cin >>n >> m;
    pre();
    for(int i  = 0; i < m; i++)
    {
        int u, v, c;
        cin >> u >> v >> c;
        Map[u][v] = c;
    }
    cout << solve(0, 0) << endl;
    //下面輸出該回路
    cout << 0;
    for(int i = Next[0]; 1; i = Next[i])
    {
        printf("->%d", i);
        if(!i)
            break;
    }
    return 0;
}