题目描述

小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。

输入描述:

城市个数n(1<n≤20,包括北京)

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

输出描述:

最小车费花销 s

示例1

输入

4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0

输出

13

说明

共 4 个城市,城市 1 和城市 1 的车费为0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,城市 1 和城市 4 之间的车费为 5,依次类推。假设任意两个城市之间均有单程票可购买,且票价在1000元以内,无需考虑极端情况。

思路:这道题我一直卡在起点的选择上,在一开始,我一度固执的以为起点不同将影响最优解。后来发现其实是无所谓的,举一个例子:

假如有5个城市编号分别为0~5,此时已知一条最优解的路径是:3->2->0->5->4->3,意味着从3出发,再回到3是最优的,我们将路径拆成两部分:3->2和2->0->5->4->3。由于路径是最优的,则我们知道从2走到3一定是最优的路径(不然不是和最优解矛盾了嘛),而3->2是一条直接路径,值是固定的,因此我们发现这条环形路径的起点就是无所谓的, 所以不妨设为0,(其实我确实想复杂了,环形路径纠结什么起点啊)。

下面是正确解法:对于旅行商这类np难的问题,在数据量不大的情况下我们可以考虑采用状态压缩的方法去枚举所走的城市路线的,例如定义dp[i][j]:表示走到城市并且状态为j的最短路径,而这个状态相信学过状态压缩的人一定不难理解,我们将经过的城市表示为二进制位里的1,例如:101就表示已经经过了城市0,和城市2,后面就是状压dp的转移了,直接看答案即可(注意内存超限问题,有点坑)。

#include<math.h>
#include<vector>
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int n,edges[25][25];
vector<vector<int>> dp;
int main(void){
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&edges[i][j]);
    int m=pow(2, n-1);
    for(int i=0;i<n;i++){
        dp.push_back(vector<int>());
        for(int j=0;j<m;j++)
            dp[i].push_back(100000);
    }
    for(int i=1;i<n;i++)
        dp[i][0]=edges[i][0];
    for(int i=1;i<m;i++){
        for(int j=0;j<n;j++){
            if(j!=0 && i>>(j-1)&1)
                continue;
            for(int k=1;k<n;k++){
                if(i>>(k-1)&1==0)
                    continue;
                if(dp[j][i]>dp[k][i^(1<<k-1)]+edges[k][j])
                    dp[j][i]=dp[k][i^(1<<k-1)]+edges[k][j];
            }
        }
    }
    printf("%d\n",dp[0][m-1]);
    return 0;
}