解题报告 之 UVA1347 Tour

解题报告 之 UVA1347 Tour

Description

Download as PDF

John Doe, a skilled pilot, enjoys traveling. While on vacation, he rents a small plane and starts visiting beautiful places. To save money, John must determine the shortest closed tour that connects his destinations. Each destination is represented by a point in the plane pi = < xiyi > . John uses the following strategy: he starts from the leftmost point, then he goes strictly left to right to the rightmost point, and then he goes strictly right back to the starting point. It is known that the points have distinct x -coordinates.

Write a program that, given a set of n points in the plane, computes the shortest closed tour that connects the points according to John's strategy.

Input 

The program input is from a text file. Each data set in the file stands for a particular set of points. For each set of points the data set contains the number of points, and the point coordinates in ascending order of the x coordinate. White spaces can occur freely in input. The input data are correct.

Output 

For each set of data, your program should print the result to the standard output from the beginning of a line. The tour length, a floating-point number with two fractional digits, represents the result.


Note: An input/output sample is in the table below. Here there are two data sets. The first one contains 3 points specified by their x and y coordinates. The second point, for example, has the x coordinate 2, and the y coordinate 3. The result for each data set is the tour length, (6.47 for the first data set in the given example).

Sample Input 

3 
1 1
2 3
3 1
4 
1 1 
2 3
3 1
4 2

Sample Output 

6.47
7.89


题目大意:有n个点,给出x、y坐标。找出一条路,从最左边的点出发,严格向右走到达最右点再严格向左回到最左点。问最短路径是多少?


分析:刚开始觉得挺难得,不太有想法,不知道怎么个DP法,后来看了一些文章,发现是货郎担问题的一种简化形式,叫做双调欧几里德旅行商问题模型,用来求货郎担问题的近似解。大概就是说规定从最左边的点出发以简化问题,然后严格向右直至最右边的点,再从最右边的点走到最左点,保证途径所有点各一次(除了最左点)且路径最小。


先看了网上的文章,发现O(n^3)的算法很难理解。。。看了半天不知道在说什么,再看紫书上O(n^2)的算法,思路比较清晰,但是一般人很难想到。下面阐述一下我的理解。大概是这么理解的:

  1.首先需要将原问题转化为,两个人A、B同时从最左边的点出发,一起严格向最右点走,且经过所有点一次(除了最左点和最右点)。这两个问题具有等价性。

  2.先自然想到用dp(i,j)表示A走到i,B走到j时的状态还需要走多远到终点(注意表示的是还有多少到终点,所以其结果与前面怎么走的无关),那么可以证明dp(i,j)==dp(j,i);这里有的人可能会疑惑为什么会相等,刚刚说过dp(i,j)表示 已经 达到这个状态后还需要走多远到达终点,与怎么到达这个状态的并没有关系,所以dp(i,j)和dp(j,i)只是两个人角色对换了而已。

  3.想到这一步之后,会出现一个问题,就是dp(i,j)无法知道i、j之间的某些点是否已经走过了,所以我们需要进一步思考,刚刚我们提到,dp(i,j)==dp(j,i),那么我们就可以始终让i>=j(等于只有终点和起点达到)。如果j>i了,只需要交换A、B的角色即可,即将i换为j,j换为i。

  4.有了这个条件之后,我们就可以规定dp(i,j)规定为:A在i,B在j(i>=j)且i之前的所有点都走过了,这样也不会漏解,为什么呢?我们的自然的方法中,之所以i~j之间有点不知道走过了没,就是因为我们允许A连续走了多步,比如A从P1->P5->P6,而B可能从P1->P2。所以P3,P4我们不知道有没有被A或者B走到,因为我们只知道A走到了P6而B走到了P2。但是你明显发现了,在刚刚那个例子中,P3、P4之后必须要被B走到。所以我们改进的dp(i,j)中可以让A和B一格一格走,要么A走,要么B走(其实只是让顺序变化了一下而已)。

  5.有了刚刚的论证,我们的状态转移就变成了下面这样:

dp[i][j] = min(DP(i + 1, j) + dist(i, i + 1), DP(i + 1, i)+dist(j,i+1));

 即要么A走,要么B走,如果A走的话,那么走到状态dp(i+1,j);如果B走,那么走到状态dp(i,i+1)到要求前面大于后面,所以dp(i,i+1)==dp(i+1,i)即可。注意dist(i,j)表示i-j的距离。


于是乎上代码:

#include
#include
#include
#include
using namespace std;

struct point
{
	double x;
	double y;
	bool operator < (const point& rhs)const
	{
		return x < rhs.x;
	}
};
point p[1010];
double dp[1010][1010];
double dis[1010][1010];

double dist(int i, int j)
{
	if (dis[i][j] >= 0) return dis[i][j];
	return dis[i][j]=sqrt((p[i].x - p[j].x)*(p[i].x - p[j].x) + (p[i].y - p[j].y)*(p[i].y - p[j].y));
}

double DP(int i, int j)
{
	if (dp[i][j] >= 0)return dp[i][j];
	dp[i][j] = min(DP(i + 1, j) + dist(i, i + 1), DP(i + 1, i)+dist(j,i+1));
	return dp[i][j];
}

int main()
{
	int n;
	while (cin >> n)
	{
		for (int i = 0; i < 1010; i++)
		for (int j = 0; j < 1010; j++)
		{
			dis[i][j] = -1.0;
			dp[i][j] = -1.0;
		}
		for (int i = 0; i < n; i++)
			cin >> p[i].x >> p[i].y;
		sort(p, p + n);
		for (int j = 0; j < n; j++)
			dp[n - 2][j] = dist(n - 2, n - 1) + dist(j, n - 1);
		printf("%.2lf\n", DP(0, 0));
	}
	return 0;
}

最后吐槽一下最近真是忙死了。。。还有校赛要来啦,,,心慌慌,心塞塞。。。

你可能感兴趣的:(ACM)