statememt
一个城市,有很多横着和竖着的街道,相邻街道之间的距离为100。横着的街道最多有 108 条,标号为 0 ~ 108−1 ,竖着的也是。(抽象成第一象限的方格平面, 0≤x,y≤108−1 ,单位长度为100)
在某些十字路口(格点)处,有 n 个半径为10的喷泉,要经过喷泉时,只能从外围绕。给你起点 s 和终点 t 的坐标,问从 s到t 的最短距离是多少?(题目保证每一行、每一列最多只有一个喷泉,且起点和终点处没有喷泉,也没有重复的喷泉)
0≤sx,sy,tx,ty<108
1≤n≤2000000≤xi,yi<108
首先保证是曼哈顿距离,然后再来考虑喷泉的情况。
经过喷泉的方式分为两种:直穿和拐穿(顾名思义,直穿是绕喷泉走 180。 ,拐穿是走 90。 )
直穿喷泉要走 10π ,拐穿喷泉要走 5π ,相比于没有喷泉要走的距离20,很明显拐穿赚了!所以就要尽可能多地拐穿,尽可能少的拐穿。
以起点s和终点t 的连线为对角线 肯定能形成一个矩形(线段在这儿就算特殊的矩形),要拐穿肯定拐穿矩形内的喷泉。将在矩形内的点按 x坐标 排序,然后在 y坐标 里找一个LIS,长度记为 len,len 就是能最多能拐穿的喷泉数。
一种特殊情况例外:当 len==min(abs(tx−sx),abs(ty−sy))+1 时,必须要直穿一个喷泉!
举个例子:起点 s=(0,1) ,终点 t=(4,3),3 个喷泉,分别为 (1,1),(2,2),(3,3) 。拐穿记为1,直穿记为0。最短距离肯定是(0, 1) -> (1,1)0 -> (2,2)1 -> (3,3)1 -> (4, 3) 或者 (0, 1) -> (1,1)1 -> (2,2)1 -> (3,3)0 -> (4, 3),长度为 740+20π 。
比赛时想到正解之后还有近两个小时的时间。那是就只考虑到 sx==sy或sy==ty 时才会直穿,当我意识到不单只有这种情况才会直穿时,已经over了。看了题解之后,思路相同,更是不快!
之后的补题,因为平时习惯以1开头,对题目中的 0 ~ 108−1 不是很注意, 以致于求LIS时出了点小差错(代码48行)
然后WA成狗:look
详见代码:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 2e5;
//const double PI = 3.1415926535897932384626;
const double PI = acos(-1);
LL sx, sy, tx, ty;
int n, tot;
double res;
struct point
{
LL x, y;
}tmp[MaxN + 5];
bool cmpx(point a, point b) {
if(a.x == b.x) return a.y < b.y;
else return a.x < b.x;
}
//----------------LIS---------------------
int len;
LL a[MaxN + 5], d[MaxN + 5];
int bin_search(LL x) {
int l = 0, r = len;
int mid = 0, ans = 0;
while(l <= r) {
mid = (l + r) >> 1;
if(d[mid] >= x) ans = mid, r = mid - 1;
else l = mid + 1;
}
return ans;
}
void get_LIS(bool c)
{
len = 0;
d[len] = -INF;
//初始d[0] == 0的,如果LIS中有0,就会少算一个
if(c == 1) {
for(int i = 1; i <= tot; i++)
a[i] = tmp[i].y;
}
else {
//求下降时将数组倒过来求最长
for(int i = 1; i <= tot; i++)
a[i] = tmp[tot - i + 1].y;
}
for(int i = 1; i <= tot; i++) {
if(a[i] > d[len]) d[++len] = a[i];
else {
int pos = bin_search(a[i]);
d[pos] = a[i];
}
}
}
//----------------------------------------
int main()
{
tot = 0;
res = 0.0;
scanf("%lld %lld %lld %lld", &sx, &sy, &tx, &ty);
if(sx > tx) {
//让起点在左边
swap(sx, tx); swap(sy, ty);
}
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
LL x, y;
scanf("%lld %lld", &x, &y);
//将在 起点和终点所形成的矩形 中的点给抠出来
if((min(sx, tx) <= x && x <= max(sx, tx)) && (min(sy, ty) <= y && y <= max(sy, ty)))
tmp[++tot].x = x, tmp[tot].y = y;
}
sort(tmp + 1, tmp + tot + 1, cmpx);
if(sy > ty) {
//取最长下降子序列
get_LIS(0);
}
else if(sy <= ty) {
//取最长上升子序列
get_LIS(1);
}
int num = min(abs(tx - sx), abs(ty - sy)) + 1;
if(len == num) {
//肯定会直线经过一个喷泉,其他都是拐角
res = (100.0 * (abs(tx - sx) + abs(ty - sy)) - 20.0 * (LL)len) + (LL)(len - 1) * 5 * PI + 10 * PI;
}
else {
//全是拐角经过
res = (100.0 * (abs(tx - sx) + abs(ty - sy)) - 20.0 * (LL)len) + (LL)len * 5 * PI;
}
printf("%.15lf\n", res);
return 0;
}