题目链接:http://soj.me/1163
问题描述:由于旅行商问题是NP问题,所有J.L.Bentley建议只考虑双调旅程来简化问题。这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。
分析:由于旅行商问题都是要求访问所有的点一遍而不重复,这里又对路径有个双调的约束,所以其实可以把问题看成是两个人同时从第一个点出发,中间分别经过不同的点(路径没有交集,但是要经过所有点),然后在最后一个点汇合,求两人路径之和的最小值。
本文中假设有n个点p1、p2、p3……pn,且n个点已按x坐标排好序。
解题思路:总体思想是动态规划,而且易知答案具有对称性(即第一个人从1走到i,第二个人从1走到j和第二个人从1走到i,第一个人从1走到j是一样的),而且除了第一个点和最后一个点,它们的路径不相交,所以只考虑i > j的情况。
状态:设dp[i, j]表示第一个人走到i,第二个人走到j(1<=j<i)时他们路径之和的最小值,这时从p1到pmax(i, j)上的点都已经经过了。
由上图可知,在状态dp[i-1,j]时(j < i-1),可以有两种状态转移方式:
1. 如(a)图,dp[i][j] = dp[i-1][j] + dist[i-1][i];(j<i-1)
2. 如(b)图,dp[i][i-1] = min(dp[i-1][k] + dist[k][i]) (1<=k<i-1)
注意这里解释一下这两种情况为什么这样转移。
首先看(a), 这种情况下,只能从i-1牵一条线到i,而不能是其它点,而且它是dp[i][j]的最小值。
再看(b),这种情况下,为什么不是dp[i-1][j] + dist[j][i],而要是min(dp[i-1][k] + dist[k][i]),我们可以这样想,我们从k(k<j)牵一条线到i,那么这样就等价于把p1-pk定位一条路线,其它的所有点都归到另一条路线,而这时的这种情况的值为dp[i-1][k] + dist[k][i],这种情况有可能比 dp[i-1][j] + dist[j][i]小。
最后,由于两条路径一定会在pn点汇合,所以,n点一定会和n-1点相连,那么我们可以通过dp[n][n-1] + dist[n][n-1]得到最终结果。
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#define N 105
#define INF 1000000000
struct Point
{
double x, y;
}p[N];
double dp[N][N], d[N][N];
//path用于记录一边的路径(或来或去)
//int path[N];
inline double dist(const Point& p1, const Point& p2)
{
return sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y));
}
//void print_path(int n)
//{
// if(n == 1)
// {
// printf("1 ");
// return;
// }
// print_path(path[n]);
// printf("%d ", n);
//}
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i=1; i<=n; ++i)
scanf("%lf %lf", &p[i].x, &p[i].y);
for(int i=1; i<=n; ++i)
for(int j=1; j<i; ++j)
d[i][j] = dist(p[i], p[j]), dp[i][j] = INF;
dp[2][1] = d[2][1];
/*path[2] = 1;*/
for(int i=3; i<=n; ++i)
{
for(int j=1; j<i-1; ++j)
dp[i][j] = dp[i-1][j] + d[i][i-1];
double tmp;
for(int k=1; k<i-1; ++k)
{
tmp = dp[i-1][k] + d[i][k];
if(tmp < dp[i][i-1])
{
dp[i][i-1] = tmp;
/*path[i] = k;*/
}
}
}
printf("%.2lf\n", dp[n][n-1] + d[n][n-1]);
/*printf("The one-side path: ");
print_path(n);
printf("\n");*/
}
return 0;
}