Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 6539 | Accepted: 3531 |
Description
The Pizazz Pizzeria prides itself in delivering pizzas to its customers as fast as possible. Unfortunately, due to cutbacks, they can afford to hire only one driver to do the deliveries. He will wait for 1 or more (up to 10) orders to be processed before he starts any deliveries. Needless to say, he would like to take the shortest route in delivering these goodies and returning to the pizzeria, even if it means passing the same location(s) or the pizzeria more than once on the way. He has commissioned you to write a program to help him.
Input
Input will consist of multiple test cases. The first line will contain a single integer n indicating the number of orders to deliver, where 1 ≤ n ≤ 10. After this will be n + 1 lines each containing n + 1 integers indicating the times to travel between the pizzeria (numbered 0) and the n locations (numbers 1 to n). The jth value on the ith line indicates the time to go directly from location i to location j without visiting any other locations along the way. Note that there may be quicker ways to go from i to j via other locations, due to different speed limits, traffic lights, etc. Also, the time values may not be symmetric, i.e., the time to go directly from location i to j may not be the same as the time to go directly from location j to i. An input value of n = 0 will terminate input.
Output
For each test case, you should output a single number indicating the minimum time to deliver all of the pizzas and return to the pizzeria.
Sample Input
3 0 1 10 10 1 0 1 2 10 1 0 10 10 2 10 0 0
Sample Output
8
题意:
有n(1<=n<=10)个城市和一个披萨店(0),要求一条最短路,从0出发又回到0
输入有n个城市,再输入一个(n+1) * (n+1)的矩阵,代表到各自的距离;
也就是TSP(旅行商)问题,一个n个点的带权的有向图,求一条路径,使得这条路经过每个点恰好一次,并且路径上边的权值和最小(或者最大);
或者求一条具有这样性质的回路;(也就是本题)
n <= 16(重要条件,状态压缩的标志)
思路:
是个最短路和状态压缩结合的问题
先预处理一下,一看给的这个矩阵,Floyd很方便处理,就这样计算出任意两点的距离dis[i][j];
接着for: 1 to (1<<n)-1 枚举所有的状态,用11位二进制表示城市和披萨店,1表示经过,0则没有
声明一个数组f[1<<11][11], f[s][i]表示 s 状态下以城市 i 为终点的最短距离,如果不能到达i城市,f[s][i]为无穷大;
(也就是TSP问题中,把披萨店和城市看做点集,dp[i][j]表示经过点集i中的点,并且以j点为终点的路径,权值和的最小值,如果这个状态不存在,就是无穷大)
由此可得出状态转移方程:f[s][i] = min{f[s][i], f[s^(1<<(i-1))][j] + dis[j][i]};//s^(1<<(i-1))表示s的不包含i的子集,因为我们要把i作为终点,要不然无意义
边界条件:dp[S][i] = a[0][i];//点集s只包括点i时的状态
s^(1<<(i-1)) 代表未到达城市i的所有状态(1<=k<=n),遍历“s点集中去掉i点的点集”中的点,要保证f[s][i]状态存在,点j到点i能连起来,dis[j][i]表示两点间最短路,状态不存在f[s][i]为无穷大;
最终结果就是min{f[(1<<n)-1][i] + dis[i][0]}(1 <= j < n》), f[(1<<n)-1][i]中的(1<<n)-1就是满状态(即全填满1,所有城市和披萨店都走了一遍了),因为还要回到披萨店,所以还要加dis[i][0];
技巧: (来自实验室聚聚)
利用2进制,使得一个整数表示一个点集,这样集合的操作可以用位运算来实现。
例如从集合i中去掉点j:
k = i & ( ~( 1 << j ) ) 或者
k = i - ( 1 << j )
遍历点集i中都包含哪些点
for(j=0;(1<<j)<=i;j++)
{
if(((1<<j)&i)!=0)
点集i就包含点j
}
把点j加入点集i:i=(i |(1<<j));
注意:
我有一点忽略掉了,每一个城市可重复访问多次!就不需要考虑f[s^(1<<(i-1))][j]和dis[j][i]的重边问题
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
int dis[11][11];
int f[1<<11][11];
/*
f[i][j]表示经过点集i中的点,并且以j点为终点的路径的权值和的最小值;
如果这个状态不存在,就是无穷大;
要求一条回路,从0出发,又回到0,而且距离最短
*/
int main()
{
int n;
int s, i, j, k;
while(scanf("%d", &n) && n)
{
//floyd求任一点最短路
for(i = 0; i <= n; i++)
for(j = 0; j <= n; j++)
scanf("%d", &dis[i][j]);
for(k = 0; k <= n; k++)
for(i = 0; i <= n; i++)
for(j = 0; j <= n; j++)
if(dis[i][j] > dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
//状态压缩
for(s = 0; s <= (1<<n)-1; s++)//枚举所有状态
for(i = 1; i <= n; i++)
if(s & (1<<(i-1)))//点集s包含点i,状态s经过第i城市
{
if(s == (1<<(i-1)))//点集s只包括点i,即状态s只经过第i城市,最短路自然就是dis[0][i]
f[s][i] = dis[0][i];
else//点集s包含多个点,即状态s要经过多个城市,第i城市只是其中一个
{
f[s][i] = 1<<29;
for(j = 1; j <= n; j++)
if(s & (1<<(j-1)) && j != i)//点集s包含点j,i为点集s终点,且j和i不能为同一城市,要不然无意义;j到i要连着,连着自然有最短路
f[s][i] = min(f[s][i], f[s ^ (1<<(i-1))][j] + dis[j][i]);
}
}
int ans = 1<<29;
for(i = 1; i <= n; i++)
ans = min(ans, f[(1<<n)-1][i] + dis[i][0]);//以0为起止的最短回路
printf("%d\n", ans);
}
return 0;
}
/*
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
----------
8
*/
反思:
还是要多积累压缩DP会用到的位运算,理解并运用;
还有边界条件,我觉得找起来还是有点迷的,要做多题,积累手感。