前言
校内模拟考试的其中一题,细节很多,测的时候 WA 了几个点,后来才改对
题面
题目链接 (luogu.org/problem/AT2702)
有一个 $10^8 \times 10^8$ 的网格图,相邻格点间上下左右的距离都是 $100m$ ,格点上面有 $n$ 个圆,每个圆半径为 $10m$ ,同一行或同一列最多只有一个圆。
现在要从 $(x_1,y_1)$ 走到 $(x_2,y_2)$ ,只能沿着网格的边或圆周走,输出最短距离(保留 13 位小数)。

样例
思路
对于圆 $i$,只有 $x_i\in\lbrack min(x_1,x_2),max(x_1,x_2)\rbrack,y_i\in\lbrack min(y_1,y_2),max(y_1,y_2)\rbrack$ 时,才是有用的。
从起点到终点,在不走回头路(例如从左下走到右上,每一步必须向右或者向上)的情况下,走过越多的圆越优($\frac{20\mathrm\pi}4<20$)。
因为不能走回头路,所以选择的圆中 $y$ 坐标是单调的。因为每行每列没有重复的圆,不难看出,即为求解满足范围内圆坐标的 $LIS$。
为了方便,如果 $y_1$ > $y_2$ ,那么交换起点和终点(使求的坐标单调上升)。
如果起点在终点左边,从左到右枚举每个点,如果该点是有用的,那么将它的坐标加入待求 $LIS$ 的数组中。如果起点在终点右边,那么倒着枚举即可。
然后对该数组求一遍 $LIS$,对于每个圆,可以使总路程减少 $20-5\mathrm\pi$ 米。
注意:如果在序列中点的个数等于起终点的间距(线的条数)(如下图),那么必然有一个点是会被经过半个圆的,总答案需要加上 $5\mathrm\pi$ 。

代码
#include<bits/stdc++.h>
#define pi 3.14159265358979323846264338327950288
using namespace std;
inline long long read(){
long long ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
long long X1,Y1,X2,Y2;
long long a[200005],c[200005],n=0,m,len,iter;
struct node{
long long x,y;
};
node tmp[200005];
bool cmp(node a,node b){
if (X1>X2){
return a.x>b.x;
}
return a.x<b.x;
}
int main(){
X1=read();Y1=read();X2=read();Y2=read();
if (Y1>Y2){
swap(X1,X2);
swap(Y1,Y2);
}
m=read();
for (long long i=1;i<=m;i++){
tmp[i].x=read(),tmp[i].y=read();
}
sort(tmp+1,tmp+1+m,cmp);
for (long long i=1;i<=m;i++){
if (min(X1,X2)<=tmp[i].x&&tmp[i].x<=max(X1,X2)){
if (min(Y1,Y2)<=tmp[i].y&&tmp[i].y<=max(Y1,Y2)){
a[++n]=tmp[i].y;
}
}
}
c[1]=a[1];
len=1;
for (long long i=2;i<=n;i++){
if (a[i]>c[len]){
len++,c[len]=a[i];
}else{
iter=lower_bound(c+1,c+len,a[i])-c,c[iter]=a[i];
}
}
if (n==0){
len=0;
}
double pre=1.00*(abs(X2-X1)+abs(Y2-Y1));
double stp=20-pi*5;
if(len==min(abs(X2-X1)+1,abs(Y2-Y1)+1)){
printf("%.13lf\n",pre*100*1.00-len*1.00*stp+pi*5);
}else{
printf("%.13lf\n",pre*100*1.00-len*1.00*stp);
}
return 0;
}
