POJ 3311 Hie with the Pie
题目大意
一个披萨店要请司机来送披萨,这家店最多接受10个订单,由于预算的问题,只能雇佣一名司机,要求用最短的时间,从披萨店出发送完最后再回到披萨店(途中可能不止一次经过一个地方)。
输入
测试数据可能有多个,第一行包含一个整数nnn (1≤n≤101 \leq n \leq 101≤n≤10),接下来的n+1n+1n+1行,每行包含n+1n+1n+1个整数,披萨点的位置000以及后面nnn个位置,其中第iii行第jjj个值表示从位置iii到达位置jjj所用的时间(其中对于从iii到jjj可能有更快捷的方式,数据不是对称的),当n=0n=0n=0时,表示输入结束
输出
对于每一个测试用例输出一个整数,表示所花费的最短的时间
样例输入
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
样例输出
8
分析
由于每两个地点的直接距离不一定是最短的,因此首先要用floydfloydfloyd算法来更新所有点之间的最短距离
然后这个问题就变成了典型的TSPTSPTSP问题,为了便捷的表示已经被访问过的位置,可以使用二进制来进行压缩,例如有444个位置,分别是0,1,2,30,1,2,30,1,2,3,其中111和222已经被访问过,那么二进制表示为(0110)2(0110)_2(0110)2,被访问过的顶点就用111表示,没访问过的用000表示。这样就可以将状态压缩到一个数组中去。就可以清楚的表示哪些顶点被访问过,哪些没有被访问。
其中d[s][j]d[s][j]d[s][j]表示,在当前的sss的状态下(已经访问过的点),正处在jjj的位置上回到顶点所花费的最小时间。状态转移方程如下
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])d[s∣1<<k][k]=min(d[s∣1<<k][k],d[s][j]+a[j][k])
其中点kkk表示下次要访问的点,是从已经访问过的点jjj转移到下一个点kkk。更新完数组后
最终答案就是,枚举所有的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),其中最小值就是答案。这句话的意思就是,访问所有顶点后,可能停留再任意一个地方iii,因此要枚举每一个地方回到000的时间。
代码
#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;
}