题目描述:
平面内给出 n 个点,记横坐标最小的点为 A,最大的点为 B,现在小 Y 想要知道在
每个点经过一次(A 点两次)的情况下从 A 走到 B,再回到 A 的最短路径。但他是个强
迫症患者,他有许多奇奇怪怪的要求与限制条件:
1.从 A 走到 B 时,只能由横坐标小的点走到大的点。
2.由 B 回到 A 时,只能由横坐标大的点走到小的点。
3.有两个特殊点 b1 和 b2, b1 在 0 到 n-1 的路上,b2 在 n-1 到 0 的路上。
请你帮他解决这个问题助他治疗吧!
输入格式:
第一行三个整数 n,b1,b2,( 0 < b1,b2 < n-1 且 b1 <> b2)。n 表示点数,从 0 到 n-1 编号,b1 和 b2 为两个特殊点的编号。
以下 n 行,每行两个整数 x、y 表示该点的坐标(0 <= x,y <= 2000),从 0 号点顺序给出。Doctor Gao 为了方便他的治疗,已经将给出的点按 x 增序排好了。
输出格式:
输出仅一行,即最短路径长度(精确到小数点后面 2 位)
样例输入:
5 1 3
1 3
3 4
4 1
7 5
8 3
样例输出:
18.18
简析:
题目大意是:给定n个点及其坐标,要求从起点和终点相向而行,使得总距离最短,并要求包含各自的指定点,且除1,n两点外,不能包含相同点。
首先,为了方便,我们将 从n向1出发的路线视为从1向n的路线,并将点的编号加一(我实在不喜欢0到n-1)。
然后,本人用dp做的。数组f[i][j]表示线路一到达i点,线路二到达j点时的总路径长度。
为了保证不出现到达同一个点的情况,同时保证每一个点都用上,我们事先找出一个目标点,再用现在的状态去更新,来判断这个点应该加入哪一条线路,规定目标点的计算方式为 max(i,j)+1。
因此,转移方程为:(dis[i][j]表示i,j间的距离
int k=max(i,j)+1;
f[1][1]=0;
f[i][k]= min(f[i][k],f[i][j]+dis[j][k])
f[k][j]= min(f[k][j],f[i][j]+dis[i][k])
当然,要考虑一些特殊情况,例如b1,b2点,k=max(i,j)+1时,(i==n||j==n)。
代码 :
#include<stdio.h>
#include<string.h>
#include<math.h>
#define max(a,b) a>=b?a:b
#define min(a,b) a>=b?b:a
int scan(){
int x=0;
char c=getchar();
while(c>'9'||c<'0')
c=getchar();
while(c>='0'&&c<='9')
x=(x<<1)+(x<<3)+c-'0',
c=getchar();
return x;
}
struct node{
int x,y;
};
int n,b1,b2;
node poi[1010];
double dis(int i,int j){
int x=(poi[i].x-poi[j].x)*(poi[i].x-poi[j].x);
int y=(poi[i].y-poi[j].y)*(poi[i].y-poi[j].y);
return sqrt(x*1.0+y*1.0);
}
double d[1010][1010],f[1010][1010];
int main(){
n=scan();
b1=scan()+1;b2=scan()+1;
for(int i=1;i<=n;i++){
poi[i].x=scan();
poi[i].y=scan();
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
d[i][j]=d[j][i]=dis(i,j);
f[i][j]=20000010.0;
}
f[1][1]=0.0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(i!=j||i==1){
int k= max(i,j) ;
k++;
if(k==n+1){
if(i!=n)
f[n][n]= min(f[n][n],f[i][j]+d[i][n]);
if(j!=n)
f[n][n]= min(f[n][n],f[i][j]+d[j][n]);
continue;
}
if(k!=b1)
f[i][k]= min(f[i][k],f[i][j]+d[j][k]);
if(k!=b2)
f[k][j]= min(f[k][j],f[i][j]+d[i][k]);
}
}
printf("%.2lf",f[n][n]);
}