给定一张 n 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点ii到jj的距离(记为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
思路:因为要遍历所有转态,而本题的状态太多(n最大为20,20个点组成的所有不同路径为20(的全排列)即20的阶乘种),无法在规定时间内完成,所以采用状态压缩,这里起点和终点是固定的,有些路径是无效的或可以去掉的(数值比较大的),所以只考虑当前哪些点走过和当前的位置在哪个点上(这里所谓的状态压缩就是用二进制的数表示状态数,二进制的每一位的数值0或1分别表示当前位的编号的点没走过和走过了,这样每一位都是0或1两种情况,一共20位,所以有2的20次方个数对应2的20次方个状态,再乘20个当前位置(终点),一共约2乘10的7次方,这样的数据范围就可以接受了),每次只由上一个对应状态(当前状态去掉当前点的状态)转移过来,计算当前最优:
状态转移: dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]);(表示当前i状态在点j时的最优解为当前最优解和由k转移到j的最优解(没有j这个点的状态的最优解+k到j的权值)两者中的min)
完整代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1<<20;
int n,dp[maxn][20],w[20][20];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>w[i][j];
}
}
memset(dp,0x3f,sizeof(dp));//因为求最小值,所以初始化正无穷
dp[1][0]=0;//一开始在起点0上,所以用二进制1表示当前第0位是1(即:0这个点走过了)
for(int i=0;i<1<<n;i++){//遍历所有状态(所有二进制数)
for(int j=0;j<n;j++){//遍历每个点(二进制的每一位)
if(i>>j&1){//当前二进制数i右移j位(就是把i的第j位移到个位),然后与个位数字1亦或就可以知道当前状态的第j个点是否走过了
for(int k=0;k<n;k++){遍历每个点(二进制的每一位),这次看j可以由那个点转移过来
if(i-(1<<j)>>k&1){//当前状态i减去2的j次方表示当前状态去掉j这个点的状态,j之前的状态,然后再按上面的方法(>>k&1),看第k位是否为1,就是是否能由k这个点转移过来
dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]);
}
}
}
}
}
cout<<dp[(1<<n)-1][n-1]<<endl;//最后2的n次方-1(即最后一个状态数(二进制的每一位全为1表示每一位都走过)并且最终在n-1这个点(终点)的最优解即为答案
return 0;
}