The shortest path
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 983 Accepted Submission(s): 490
Before you reach the rightmost point Pn, you can only visit the points those have the bigger x-coordinate value. For example, you are at Pi now, then you can only visit Pj(j > i). When you reach Pn, the rule is changed, from now on you can only visit the points those have the smaller x-coordinate value than the point you are in now, for example, you are at Pi now, then you can only visit Pj(j < i). And in the end you back to P1 and the tour is over.
You should visit all points in this tour and you can visit every point only once.
3 1 1 2 3 3 1
6.47 Hint: The way 1 - 3 - 2 - 1 makes the shortest path.
原题大意:平面n个不同的点(假设任意两点x坐标不同),旅程从最左点开始,严格地从左到右直至最右点,然后严格地从右到左至最左点。求最短双调路线。每个点只能走一次。
解法:
(1)对n个点按x坐标排序,我们用dp[i][j]表示其中一条路径走到i点,另一条路径走到j点(且i和j并不相连);则dp[i][j]==dp[j][i](想一想为什么),所以我们仅需要计算一个,我们只计算dp[i][j] (i<=j的情况)。另外,由几何学可以知道,如果存在交错路径,那该双调路径必然不是最优路径(我也不会从数学理论上严格证明T_T,但是画一画图很直观)。因此计算dp[i][i]没有意义(除了最右边一个点)。
(2)分两种情况:1. i<j-1,这种情况下,dp[i][j]只能由dp[i][j-1]进行转移,因为路径要求严格从左至右(到达最右点之后严格从右至左),所以能到达j点的只能是点j-1,否则的话j-1点没有被经过,与题设严格从左至右矛盾。此时dp[i][j]=dp[i][j-1]+dis[j-1][j]; 2. i==j-1,这种情况下,j点必由某一点k直接到达,则路径可分为一条路径从1到i;另一条路径从1到k,然后从k到j。计算方程时应该这么算:dp[i][j]=min(dp[i][j], dp[i][k]+dis[k][j]),有童鞋就疑问了,dis[i][k]此时肯定没有被计算啊,因为这种情况下必定有k<i,没错,dis[i][k]没有被计算,但是dis[k][i]计算过了,而且dis[i][k]和dis[k][i]是等价的,所以方程可改写为:dp[i][j]=min(dp[i][j], dp[k][i]+dis[k][j])。还有童鞋疑问,那么从点k+1到点j-2之间的点会不会有遗漏呢?肯定不会!因为这种情况下按题设和转移方程,点k+1,k+2,....,j-2肯定在从1到j-1(即从1到i)这条路径上。总之,一旦你觉得方程有问题可能会遗漏点,就想一下题设:严格从左至右,然后仔细理解一下第一种情况下的方程,问题就迎刃而解了。初始dp[1][2]=dis[1][2],最终结果为dp[n-1][n]+dis[n-1][n] (n==2的情况单独处理)。
(3)复杂度
排序O(n*logn);
对于每个j,若i<j-1,则求dp[i][j]的转移费用为O(1),若i==j-1,则转移费用O(n),所以对于每一个j转移费用为O(n)+O(n)=O(n),共有n个j,所以总费用为O(n^2)。
所以复杂度为O(n*logn)+O(n^2)=O(n^2)。
<pre name="code" class="cpp">#include<stdio.h>
#include<math.h>
const int N =205;
const double inf =99999999;
double distance(double x,double y)
{
return sqrt(x*x+y*y);
}
double Min(double a,double b)
{
return a<b?a:b;
}
int main()
{
double dis[N][N],x[N],y[N],dp[N][N];
//dp[i][j]代表0-->i,0-->j,但i与j不相连,属于两个不同的路径,没有公共点(除了0点)
int n;
while(scanf("%d",&n)>0)
{
for(int i=0;i<n;i++)
scanf("%lf%lf",&x[i],&y[i]);
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
dis[i][j]=dis[j][i]=distance(x[i]-x[j],y[i]-y[j]);
dp[0][1]=dis[0][1];
for(int i=2;i<n;i++)
{
for(int j=0;j<i-1;j++)
dp[j][i]=dp[j][i-1]+dis[i-1][i];
dp[i-1][i]=inf;
for(int j=0;j<i-1;j++)
dp[i-1][i]=Min(dp[i-1][i],dp[j][i-1]+dis[j][i]);
}
dp[n-1][n-1]=dp[n-2][n-1]+dis[n-2][n-1];
for(int j=0;j<n-1;j++)
dp[n-1][n-1]=Min(dp[n-1][n-1],dp[j][n-1]+dis[j][n-1]);
printf("%.2lf\n",dp[n-1][n-1]);
}
}