Hie with the Pie(Floyd 状压DP)

POJ 3311 Hie with the Pie

题目大意

一个披萨店要请司机来送披萨,这家店最多接受10个订单,由于预算的问题,只能雇佣一名司机,要求用最短的时间,从披萨店出发送完最后再回到披萨店(途中可能不止一次经过一个地方)。

输入

测试数据可能有多个,第一行包含一个整数 n n n ( 1 ≤ n ≤ 10 1 \leq n \leq 10 1n10),接下来的 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 0123,其中 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 &lt; &lt; k ] [ k ] = m i n ( d [ s ∣ 1 &lt; &lt; k ] [ k ] , &ThinSpace;&ThinSpace; d [ s ] [ j ] + a [ j ] [ k ] ) d[s|1&lt;&lt;k][k] = min(d[s|1&lt;&lt;k][k],\,\,d[s][j]+a[j][k]) d[s1<<k][k]=min(d[s1<<k][k],d[s][j]+a[j][k])

其中点 k k k表示下次要访问的点,是从已经访问过的点 j j j转移到下一个点 k k k。更新完数组后

最终答案就是,枚举所有的 d [ ( 1 &lt; &lt; n ) − 1 ] [ i ] + a [ i ] [ 0 ] &ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace; ( 0 &lt; i &lt; n ) d[(1 &lt;&lt; n) - 1][i] + a[i][0]\,\,\,\,\,\,(0&lt;i&lt;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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值