最短Hamilton路径(状压dp)

最短Hamilton路径实际上就是状压dp,而且这是一道作为一个初学状压dp的我应该必做的题目

题目描述

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

输入

第一行一个整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10^7的正整数,记为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路径的长度。

样例数据

4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0

思路讲解

作为一道最基础的状压dp ,我们需要掌握它为什么是这么做的。

我作为一名菜鸡,首先想到的就是朴素做法,可是朴素做法它的时间复杂度不允许我通过这道题目。  

那我们分析一下朴素做法,从起点到终点每个点只经过一次且求最短路径,,,嗯,最暴力的话就是我们应该把所有的不同种路径全都枚举出来(这个当然就是全排列啦),然后去比较寻找最短路径。

那么这个复杂度是O(n*n !),因为我们枚举所有情况是O(n!),然后每一种路径求和是O(n)的,所以总复杂度是O(n*n!),这个不难分析。

但是我们想了,这么大的复杂度该怎么办呢?  我们再来想想 ,我们的复杂度之所以大是因为  “  n!  ” ,所以我们试图从这里想想办法。

枚举每一位,所有种方案,,,我们可以用二进制,因为二进制同样可以把一组数表示出来,,所以我们想到了用状压去做。

我们定义f(i,j) i 表示的是当前的二进制数  , j 表示的是当前所到达的二进制的第j位  

在任意时刻,我们还需要知道当前所处的位置,因此我们用f(i ,j )表示“点被经过的状态” 对应的二进制数位i 且目前处于j时的最短路径。

在任意时刻,有公式f【i,j】=min(i xor (1<<j)  ,    k )+a(k,j)     a数组表示从k到j的路径大小。

i xor  (1<<j ) 表示的是在上一时刻我们所处位置是的路径和

所以公式的意思就明白了  ,就是上一个时刻的路径和与当前时刻的路径和大小的比较

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n ;
int a[25][25];   // 代表i到j路径花费 
int f[1<<22][25];   //利用二进制的思想 , i表示的是当前的二进制数, j表示的是当前所到达的二进制的第j位 
int main(){
    scanf("%d",&n);
    for(int i=0 ;i<n;i++){
       for(int j=0 ;j<n;j++){
           scanf("%d",&a[i][j]);
       }    
     }
   
    memset(f,127,sizeof(f));  
    f[1][0] = 0 ;
    for(int i=1 ;i<(1<<n);i++){
        for(int j=0 ; j<n ;j++){
            if((i>>j)&1){     //表示的是我们枚举二进制数的时候  ,这个数中第j位是不是已经被选中了,如果没选过,那我们还用它干嘛。最后的结果不就是n-1个数全部选中嘛
                for(int k=0 ;k<n ;k++ ){
                    if((i>>k)&1){
                        f[i][j] = min(f[i][j], f[i^(1<<j)][k]+a[k][j]) ;
                    }
                }
            }
        }
    } 
    cout << f[(1<<n)-1][n-1]<<endl;
    return 0 ;
}

猜你喜欢

转载自www.cnblogs.com/xiaoK-778697828/p/10152692.html