POJ 3311 Hie with the Pie
题目大意
一个披萨店要请司机来送披萨,这家店最多接受10个订单,由于预算的问题,只能雇佣一名司机,要求用最短的时间,从披萨店出发送完最后再回到披萨店(途中可能不止一次经过一个地方)。
输入
测试数据可能有多个,第一行包含一个整数 n n n ( 1 ≤ n ≤ 10 1 \leq n \leq 10 1≤n≤10),接下来的 n + 1 n+1 n+1行,每行包含 n + 1 n+1 n+1个整数,披萨点的位置 0 0 0以及后面 n n n个位置,其中第 i i i行第 j j j个值表示从位置 i i i到达位置 j j j所用的时间(其中对于从 i i i到 j j j可能有更快捷的方式,数据不是对称的),当 n = 0 n=0 n=0时,表示输入结束
输出
对于每一个测试用例输出一个整数,表示所花费的最短的时间
样例输入
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
样例输出
8
分析
由于每两个地点的直接距离不一定是最短的,因此首先要用 f l o y d floyd floyd算法来更新所有点之间的最短距离
然后这个问题就变成了典型的 T S P TSP TSP问题,为了便捷的表示已经被访问过的位置,可以使用二进制来进行压缩,例如有 4 4 4个位置,分别是 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3,其中 1 1 1和 2 2 2已经被访问过,那么二进制表示为 ( 0110 ) 2 (0110)_2 (0110)2,被访问过的顶点就用 1 1 1表示,没访问过的用 0 0 0表示。这样就可以将状态压缩到一个数组中去。就可以清楚的表示哪些顶点被访问过,哪些没有被访问。
其中 d [ s ] [ j ] d[s][j] d[s][j]表示,在当前的 s s s的状态下(已经访问过的点),正处在 j j j的位置上回到顶点所花费的最小时间。状态转移方程如下
d [ s ∣ 1 < < k ] [ k ] = m i n ( d [ s ∣ 1 < < k ] [ k ] ,    d [ s ] [ j ] + a [ j ] [ k ] ) d[s|1<<k][k] = min(d[s|1<<k][k],\,\,d[s][j]+a[j][k]) d[s∣1<<k][k]=min(d[s∣1<<k][k],d[s][j]+a[j][k])
其中点 k k k表示下次要访问的点,是从已经访问过的点 j j j转移到下一个点 k k k。更新完数组后
最终答案就是,枚举所有的 d [ ( 1 < < n ) − 1 ] [ i ] + a [ i ] [ 0 ]        ( 0 < i < n ) d[(1 << n) - 1][i] + a[i][0]\,\,\,\,\,\,(0<i<n) d[(1<<n)−1][i]+a[i][0](0<i<n),其中最小值就是答案。这句话的意思就是,访问所有顶点后,可能停留再任意一个地方 i i i,因此要枚举每一个地方回到 0 0 0的时间。
代码
#include <cstdio>
#include <cstring>
#define N 11
#define INF 0x3f3f3f3f
#define min(a,b) a>b?b:a
int a[N][N];
int d[1 << N][N];
int n;
inline void floyd() {
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
for(int k = 0; k < n; k++)
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
}
int main () {
while(1) {
scanf("%d", &n);
if(!n) break;
n++;
memset(d, INF, sizeof(d)); //初始化
d[1][0] = 0; //从0出发,二进制中1表示第一个位置访问过,所以置为0
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
scanf("%d", &a[i][j]);
floyd();
for(int s = 0; s < 1 << n; s++)
for(int j = 0; j < n; j++)
if(s >> j & 1) //找到已经访问过的点j
for(int k = 0; k < n; k++)
if(!(s >> k & 1)) //找到没有被访问过的点k
d[s|1 << k][k] = min(d[s|1 << k][k], d[s][j] + a[j][k]); //从j点更新到k点
int ans = INF;
for(int i = 1; i < n; i++)
ans = min(ans, d[(1 << n)-1][i] + a[i][0]);
printf("%d\n", ans);
}
return 0;
}