旅行商问题

状态压缩dp的典范之一。今天来学习一下。
一般问题都会这样子问:从某个点出发,经过所有点(可能是部分点)后回到原点所需最小距离。
暴力时间复杂度为阶乘显然不过关,使用状态压缩dp能达到2^n,一般点的个数为20个以内。
首先复习一下集合的操作:
1.全集M的子集:for i in range(M - 1)
2.某个子集A的补集:(M - 1)^ A
3.lowbit(最第非0位) X&(-X)
4. 遍历某个集合s的所有子集:
for (int i = s;i > 0;i = s&(i - 1)
5.A,B并集:A | B
6.A,B交集:A & B
这些是一些经常会用到的方法
解决实际问题:
小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。

输入描述:
城市个数n(1

城市间的车票价钱 n行n列的矩阵 m[n][n]

输出描述:
最小车费花销 s

输入例子1:
4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0

输出例子1:
13
状态转移方程:dp[j][i]表示我们遍历集合j且最后一个到达的节点为i的最小路程
代码:

const int max_v = 0x3f3f3f3f;
    int n;
    cin >> n;
    vector<vector<int>>res(n, vector<int>(n, 0));
    int M = (1 << n);
    vector<vector<int>>dp(M, vector<int>(n,max_v));
    int t;
    for (int i = 0; i < n; i++) {
     
        for (int j = 0; j < n; j++) {
     
            cin >> t;
            res[i][j] = t;
        }
    }
    dp[1][0] = 0;//起始状态,集合为1(1<<0,集合中单一个节点0),且0为最后到达的节点。此时代价为0
    for (int i = 1; i < M; i++) {
     //遍历全集所有子集1~M - 1
        for (int j = 1; j < n; j++) {
     //从集合i为基,将所有未加入集合的点都加入一遍。
            if ((i & (1 << j))) {
     //j已在集合中跳过
                continue;
            }
            if (!(i & 1)) {
      continue; }//由于0为起始点(最后才回到0)所以可以直接跳过含有0的集合,剪枝。
            for (int k = 0; k < n; k++) {
     //加入策略:遍历本集合中的所有点,选取最优解。
                if (((1 << k) & i)) {
     
                    dp[i | (1 << j)][j] = min(dp[i | (1<<j)][j],dp[i][k] + res[k][j]);
                    //i | (1<
                    //dp[i][k]表示集合i且最后达到的点为k,从k再到更新最优值。
                }
            }
        }
    }
    int ans = max_v;
    for (int i = 0; i < n; i++) {
     //回0
        ans = min(dp[M - 1][i] + res[i][0],ans);
    }
    cout << ans;

你可能感兴趣的:(状态压缩dp,算法,动态规划)