题目大意:
平面内给出 n 个点,记横坐标最小的点为 A,最大的点为 B,现在Zxd想要知道在每个点经过一次(A 点两次)的情况下从 A 走到 B,再回到 A 的最短路径。但他是个强迫症患者,他有许多奇奇怪怪的要求与限制条件:
1. 从 A 走到 B 时,只能由横坐标小的点走到大的点。
2. 由 B 回到 A 时,只能由横坐标大的点走到小的点。
3. 有两个特殊点 b1 和 b2, b1 在 0 到 n-1 的路上,b2 在 n-1 到 0 的路上。
请你帮他解决这个问题助他治疗吧!
思路:
欧拉回路?最短路?费用流?不,是DP。
要是没有两个特殊点的存在,这道题就是一个裸的欧拉回路。
但是有了这两个点,就是DP了。
当我们从点1到达点n时,要从点n回点1时,可以发现,这是一个无向图,从点n到点1的路径就是从点1到点n的路径!
那么这道题就转化为:求两条从点1到点n的边,要求这两条路径互不干涉,每一条路径经过一个特殊点,且要求路径长度之和最短。
设f[i][j]为第一条路径到达点i,第二条路径到达点j的最短路径和,那么就有
k=max(i,j)+1
f[k][j]=min(f[k][j],f[i][j]+dis[i][k])
f[i][k]=min(f[i][k],f[i][j]+dis[j][k])
最终任意一条路径到达终点时
f[n][n]=min(f[n][n],f[i][j]+dis[i][n])
f[n][n]=min(f[n][n],f[i][j]+dis[j][n])
代码:
#include <cstdio> #include <iostream> #include <cmath> #include <cstring> using namespace std; double f[1011][1011],x[1011],y[1011],dis[1011][1011]; int n,b1,b2,k; int main() { freopen("path.in","r",stdin); freopen("path.out","w",stdout); scanf("%d%d%d",&n,&b1,&b2); for (int i=1;i<=n;i++) { scanf("%lf%lf",&x[i],&y[i]); for (int j=1;j<i;j++) dis[i][j]=dis[j][i]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); //求直线距离 } for (int i=0;i<=n;i++) for (int j=0;j<=n;j++) f[i][j]=2147483647; //初始化 f[1][1]=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { if (i==j&&i>1) continue; //判断 k=max(i,j)+1; if (k>n) //到达终点 { if (i<n) f[n][n]=min(f[n][n],f[i][j]+dis[i][n]); if (j<n) f[n][n]=min(f[n][n],f[i][j]+dis[j][n]); } else //没到达终点 { if (k!=b2+1) f[k][j]=min(f[k][j],f[i][j]+dis[i][k]); if (k!=b1+1) f[i][k]=min(f[i][k],f[i][j]+dis[j][k]); } } printf("%0.2lf\n",f[n][n]); return 0; }