(AtCoder Grand Contest 019) C - Fountain Walk

题目链接

statememt

题目大意

一个城市,有很多横着和竖着的街道,相邻街道之间的距离为100。横着的街道最多有 108 条,标号为 0 ~ 1081,竖着的也是。(抽象成第一象限的方格平面, 0x,y1081 ,单位长度为100)
在某些十字路口(格点)处,有 n 个半径为10的喷泉,要经过喷泉时,只能从外围绕。给你起点s和终点 t 的坐标,问从st的最短距离是多少?(题目保证每一行、每一列最多只有一个喷泉,且起点和终点处没有喷泉,也没有重复的喷泉)

数据范围

0sx,sy,tx,ty<108
1n2000000xi,yi<108

解题思路

首先保证是曼哈顿距离,然后再来考虑喷泉的情况。
经过喷泉的方式分为两种:直穿和拐穿(顾名思义,直穿是绕喷泉走 180 ,拐穿是走 90
直穿喷泉要走 10π ,拐穿喷泉要走 5π ,相比于没有喷泉要走的距离20,很明显拐穿赚了!所以就要尽可能多地拐穿,尽可能少的拐穿。
st 的连线为对角线 肯定能形成一个矩形(线段在这儿就算特殊的矩形),要拐穿肯定拐穿矩形内的喷泉。将在矩形内的点按 x 排序,然后在 y 里找一个LIS,长度记为 lenlen 就是能最多能拐穿的喷泉数。

一种特殊情况例外:当 len==min(abs(txsx),abs(tysy))+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==sysy==ty 时才会直穿,当我意识到不单只有这种情况才会直穿时,已经over了。看了题解之后,思路相同,更是不快!
之后的补题,因为平时习惯以1开头,对题目中的 0 ~ 1081不是很注意, 以致于求LIS时出了点小差错(代码48行)
然后WA成狗:look


详见代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>
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;
}
AtCoder Practice Contest #B - インタラクティブ練習 (Interactive Sorting) 是一道比较有趣的题目。它是一道交互式的排序题目,需要你与一个神秘程序进行交互,以便将一串无序的数字序列排序。 具体来说,这个神秘程序会给你一个长度为 $N$ 的数字序列,然后你需要通过询问它两个数字的大小关系,来逐步确定这个序列的排序顺序。每次询问之后,神秘程序都会告诉你两个数字的大小关系,比如第一个数字比第二个数字小,或者第二个数字比第一个数字小。你需要根据这个信息,来调整这个数字序列的顺序,然后再向神秘程序询问下一对数字的大小关系,以此类推,直到这个数字序列被完全排序为止。 在这个过程中,你需要注意以下几点: 1. 你最多只能向神秘程序询问 $Q$ 次。如果超过了这个次数,那么你的程序会被判定为错误。 2. 在每次询问之后,你需要及时更新数字序列的顺序。具体来说,如果神秘程序告诉你第 $i$ 个数字比第 $j$ 个数字小,那么你需要将这两个数字交换位置,以确保数字序列的顺序是正确的。如果你没有及时更新数字序列的顺序,那么你的程序也会被判定为错误。 3. 在询问的过程中,你需要注意避免重复询问。具体来说,如果你已经询问过第 $i$ 个数字和第 $j$ 个数字的大小关系了,那么你就不需要再次询问第 $j$ 个数字和第 $i$ 个数字的大小关系,因为它们的大小关系已经被确定了。 4. 在排序完成之后,你需要将排序结果按照从小到大的顺序输出。如果你输出的结果不正确,那么你的程序也会被判定为错误。 总的来说,这道题目需要你熟练掌握交互式程序设计的技巧,以及排序算法的实现方法。如果你能够熟练掌握这些技巧,那么就可以顺利地完成这道非传统题了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值