Hie with the Pie(Floyd 状压DP)

本文深入探讨了POJ3311HiewiththePie问题,通过Floyd算法更新最短路径,采用TSP问题的二进制状态压缩动态规划解决披萨配送问题,实现最短时间完成配送。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

POJ 3311 Hie with the Pie

题目大意

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

输入

测试数据可能有多个,第一行包含一个整数nnn (1≤n≤101 \leq n \leq 101n10),接下来的n+1n+1n+1行,每行包含n+1n+1n+1个整数,披萨点的位置000以及后面nnn个位置,其中第iii行第jjj个值表示从位置iii到达位置jjj所用的时间(其中对于从iiijjj可能有更快捷的方式,数据不是对称的),当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,30123,其中111222已经被访问过,那么二进制表示为(0110)2(0110)_2(0110)2,被访问过的顶点就用111表示,没访问过的用000表示。这样就可以将状态压缩到一个数组中去。就可以清楚的表示哪些顶点被访问过,哪些没有被访问。

其中d[s][j]d[s][j]d[s][j]表示,在当前的sss的状态下(已经访问过的点),正处在jjj的位置上回到顶点所花费的最小时间。状态转移方程如下

d[s∣1&lt;&lt;k][k]=min(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])

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

最终答案就是,枚举所有的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),其中最小值就是答案。这句话的意思就是,访问所有顶点后,可能停留再任意一个地方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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值