旅行商问题 (状压dp入门)

题面

给定一张 n 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

输入格式
第一行输入整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(记为a[i,j])。

对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。

输出格式
输出一个整数,表示最短Hamilton路径的长度。

数据范围
1≤n≤20
0≤a[i,j]≤107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18

思路

状压dp的入门题目,本题数据量是二十,哈密顿周游问题是一个np完全问题,所以我们可以暴力解,那么暴力枚举复杂度20的阶乘,大概在18次方左右,显然我们不能接受。所以我们需要考虑状态压缩。首先我们想,我们有四个点,1234,我们要从1走到4,两种走法,1->2->3>4和1->3->2->4,我们假设第一种走法距离为2,第二种走法,距离为3,那么,在后续的路程中,如果前半段是这样的一个走法,后半段的每一个走法,这两个方案都会一一对应,然而第一个方案总比第二个小1,所以我们不需要枚举第二种方案就可以取得最优值,这就是dp的思想。那么,我们如何枚举哪,基于上面的论述,我们所需要的的条件就是我们的终点和我们所走过的点,那么我们走过的点怎么枚举呢,这就要用到二进制数来进行一个状态的压缩了。我们一共的状态就是1<<20个,然后进行一个状态的转移就好了。当然我们在枚举的时候可以进行一个剪枝,就是我们要去掉目前的终点枚举上一个状态的时候,我们可以判断一下,这个终点是否在我上层循环的状态里面,后面也是一样。

代码实现

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int M=1<<20;
const int N=20;
int f[M][N],maze[N][N];
int n;
int main () {
    cin>>n;
    for (int i=0;i<n;i++)
     for (int j=0;j<n;j++) cin>>maze[i][j];
    memset (f,0x3f3f3f3f,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) {
                  f[i][j]=min (f[i][j],f[i-(1<<j)][k]+maze[k][j]);
              }
          }
      }
    cout<<f[(1<<n)-1][n-1]<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/hhlya/p/13305987.html