【题解】[AGC019C] Fountain Walk【保姆级】

【题解】[AGC019C] Fountain Walk【保姆级】

前言

楼中的 Ebola 是我的老师,可以认为我的题解更加“平民化”,放心食用!

前置题目

【模板】最长公共子序列

分析问题

简化题目

给定一个只有第一象限的直角坐标系,每两条横行纵列上,只能有一个“喷泉”,半径为 \(10\),坐标系的单位长度为 \(100\),给定两个点,求两者之间的最短距离。

假设

起点在左下,终点在右上。

Solve-1 走走看

也就是说我们会不断的往右上方走,遇到喷泉就绕 \(1/4\) 周转弯,通过这样,我们可以把原来的转弯的 \(20\) 优化成 5π。
但如果绕 \(1/2\) 圆周,你一定又会很容易的想到两点之间线段最短!
因此,我们的策略便是:

我们希望在往右上走的过程中,尽可能遇到多的圆盘!

Solve-2 怎么走

因为我们要一直往右上走,绝对不可以回头或者向下,也就是说,我们希望喷泉可以一直往右上,横纵坐标依次 上升

但是,喷泉的位置是随机的!可是,喷泉仍然有规律可循:喷泉的横坐标从左往右可以依次 遍历上升

我们可以想象这是某个数组的 下标,然后喷泉的纵坐标为数组对应的内容,那么解决问题的关键便是 最长上升子序列

Solve-3 优化呗

保留

保留由起点和终点的横纵坐标包围而成的矩形!(这很好理解)

scanf("%lld%lld%lld%lld", &ax, &ay, &bx, &by);
lt minx = min(ax, bx), maxx = max(ax, bx);
lt miny = min(ay, by), maxy = max(ay, by);
scanf("%lld", &n);
while(n--){
    scanf("%lld%lld", &a, &b);
    if(a >= minx && a <= maxx && b >= miny && b <= maxy)
        g.push_back({a, b});
}

镜像翻转

上述的内容都基于:起点在左下,终点在右上。如果以此类推,那么你一定会崩溃!

这时,我们只需要通过镜像翻转的技巧!

  1. 改变坐标的数据,让起点成为坐标原点!

  2. 这时你会很容易的想到不断通过对称,将终点的新坐标变换到第一象限即可,也就是横纵坐标皆为正数!

//以起点a为坐标原点(镜像翻转时我只需要让所有的点在第一象限(也就是说横纵坐标都为正数)。
void chg(){
    bx = bx - ax > 0 ? bx - ax : ax - bx;
    by = by - ay > 0 ? by - ay : ay - by;
    for(T v : g){
        v.x = v.x - ax > 0 ? v.x - ax : ax - v.x;
        v.y = v.y - ay > 0 ? v.y - ay : ay - v.y;
        as[++num].y = v.y, as[num].x = v.x;
    }
    ax = 0, ay = 0;
}

离散化

实话说,不离散化,我真不道道友要如何完成 LIS,这是非常重要的操作,否则不管是数组大小还是求解,难度和内存上都是说不过去的!

for(T v : g) as[++num].y = v.y, as[num].x = v.x;

计算

找规律的东西不会有人要暴力模拟吧。 其实只要找到要经过几个喷泉就可以!

到时这里有一个大问题:

若每一行都经过一个圆盘,或每一列都经过一个圆盘,则答案加上1/4圆周的长度!(应该好理解)

z = LIS();//求最多经过几个
ans = (bx - ax + by - ay) * dl;
ans = ans - z * r * 2;
ans = ans + (double)z * (double)5 * pi;
if(z == by - ay + 1 || z == bx - ax + 1) 
    ans = ans + (double)5 * pi;

提醒

  • 半径的大小为 \(10\)!!!
  • 我们只需要 \(11\) 位的精度!!!

Code

#include<bits/stdc++.h>
using namespace std;

typedef long long lt;
const lt N = 2e5 + 10, dl = 100, r = 10;
const long double pi = 3.141592653589793;
lt ax, ay, bx, by, a, b, z;
long double ans;

lt n, num;
struct T{
    lt x, y;
};
vector<T> g;

T as[N];
lt d[N], L;

bool cmp(T a, T b){
    return a.x < b.x;
}

void scin(){//输入
    scanf("%lld%lld%lld%lld", &ax, &ay, &bx, &by);
    lt minx = min(ax, bx), maxx = max(ax, bx);
    lt miny = min(ay, by), maxy = max(ay, by);
    scanf("%lld", &n);
    while(n--){
        scanf("%lld%lld", &a, &b);
        if(a >= minx && a <= maxx && b >= miny && b <= maxy)
            g.push_back({a, b});
    }
}

void chg(){
    bx = bx - ax > 0 ? bx - ax : ax - bx;
    by = by - ay > 0 ? by - ay : ay - by;
    for(T v : g){
        v.x = v.x - ax > 0 ? v.x - ax : ax - v.x;
        v.y = v.y - ay > 0 ? v.y - ay : ay - v.y;
        as[++num].y = v.y, as[num].x = v.x;
    }
    ax = 0, ay = 0;
}

lt LIS(){
    sort(as+1, as+1+num, cmp);
    for(int i = 1; i <= num; i++){
        if(as[i].y >= d[L]){
            L++;
            d[L] = as[i].y;
        }
        else{
            int l = upper_bound(d+1, d+1+L, as[i].y) - d;
            d[l] = as[i].y;
        }
    }
    return L;
}

void count(){
    ans = (bx - ax + by - ay) * dl;
    ans = ans - z * r * 2;
    ans = ans + (double)z * (double)5 * pi;
    if(z == by - ay + 1 || z == bx - ax + 1) ans = ans + (double)5 * pi;
}

int main(){
    scin();//输入

    chg();
    //以起点a为坐标原点(镜像翻转时我只需要让所有的点在第一象限(也就是说横纵坐标都为正数)。

    z = LIS();//求最多经过几个

    count();//计算~

    printf("%.15Lf", ans);
    return 0;
}
//虽然答案要保留15位小数输出,但是实际只要11位的精度

后记

  • 愿诸君如愿以偿。
posted @ 2024-04-26 01:08  元越  阅读(8)  评论(1编辑  收藏  举报