bzoj 2433 [Noi2011]智能车比赛 [计算几何+spfa]

Description

 新一届智能车大赛在JL大学开始啦!比赛赛道可以看作是由n个矩形区域拼接而成(如下图所示),每个矩形的边都平行于坐标轴,第i个矩形区域的左下角和右上角坐标分别为(xi,1,yi,1)和(xi,2,yi,2)。

题目保证:xi,1<xi,2=xi+1,1,且yi,1< yi,2,相邻两个矩形一定有重叠在一起的边(如图中虚线所示),智能车可以通过这部分穿梭于矩形区域之间。

图1

选手们需要在最快的时间内让自己设计的智能车从一个给定的起点S点到达一个给定的终点T点,且智能车不能跑出赛道。假定智能车的速度恒为v且转向不消耗任何时间,你能算出最快需要多少时间完成比赛么?

Input

 输入的第一行包含一个正整数n,表示组成赛道的矩形个数。

接下来n行描述这些矩形,其中第i行包含4个整数xi,1, yi,1, xi,2, yi,2,表示第i个矩形左下角和右上角坐标分别为(xi,1, yi,1)和(xi,2, yi,2)。

接下来一行包含两个整数xS, yS,表示起点坐标。

接下来一行包含两个整数xT, yT,表示终点坐标。

接下来一行包含一个实数v,表示智能车的速度。

Output

 仅输出一个实数,至少精确到小数点后第六位,为智能车完成比赛的最快时间。

对于每个测试点,如果你的输出结果和参考结果相差不超过10^-6,该测试点得满分,否则不得分。

Sample Input

2 
1 1 2 2 
2 0 3 4 
1 1 
3 0 
1.0

Sample Output

2.41421356

HINT

N<=2000,所输入数字为绝对值小于40000的整数

Solution

最短路的做法:
从i能到j,就在i,j之间连一条边,然后跑spfa。
然而建图是 n3 的。
于是我们在每次松弛的时候,维护一个视野的上下界,假设当前在点i,然后依次枚举点j,如果j在视野内就更新dist值,然后再用点j来更新视野。
就酱。

#include <bits/stdc++.h>

using namespace std;

const double INF = 1e20;

struct Point{
    double x, y;
    Point (double a = 0, double b = 0) : x(a), y(b) {}
};

int n, st;
double v;
Point p[8005], S, T;
double dist[8005];
bool vis[8005];

double getdist(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

double cross(Point o, Point a, Point b) {
    double x1 = a.x - o.x, y1 = a.y - o.y;
    double x2 = b.x - o.x, y2 = b.y - o.y;
    return x1 * y2 - x2 * y1;
}

bool check(int up, int low, Point a, Point b) {
    return !(up && cross(a, p[up], b) > 0 || low && cross(a, p[low], b) < 0);
}

void spfa() {
    for (int i = st; i <= n; i++)
        dist[i] = INF;
    dist[st] = 0;
    vis[st] = 1;
    queue<int> q;
    q.push(st);
    while (!q.empty()) {
        int cur = q.front();
        q.pop();
        int up = 0, low = 0;
        for (int i = cur + 1; i <= n; i++) {
            if (check(up, low, p[cur], p[i])) {
                if (dist[cur] + getdist(p[cur], p[i]) < dist[i]) {
                    dist[i] = dist[cur] + getdist(p[cur], p[i]);
                    if (!vis[i]) q.push(i);
                }
            }
            if (((i - 1) % 4 + 1) & 1 && (!up || cross(p[cur], p[up], p[i]) <= 0)) up = i;
            else if (!(((i - 1) % 4 + 1) & 1) && (!low || cross(p[cur], p[low], p[i]) >= 0)) low = i;
            if (up && low && cross(p[cur], p[up], p[low]) > 0) break;
        }
        vis[cur] = 0;
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lf %lf %lf %lf", &p[i * 4 - 2].x, &p[i * 4 - 2].y, &p[i * 4 - 1].x, &p[i * 4 - 1].y);
        p[i * 4 - 3] = Point(p[i * 4 - 2].x, p[i * 4 - 1].y);
        p[i * 4] = Point(p[i * 4 - 1].x ,p[i * 4 - 2].y);
    }
    n <<= 2;
    scanf("%lf %lf %lf %lf", &S.x, &S.y, &T.x, &T.y);
    scanf("%lf", &v);
    if (S.x > T.x) swap(S, T);
    for (; n; n--)
        if (p[n].x <= T.x) break;
    p[++n] = T;
    for (st = 1; st <= n; st++)
        if (p[st].x >= S.x) break;
    p[--st] = S;
    spfa();
    printf("%.10lf\n", dist[n] / v);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值