【BZOJ 2433】[Noi2011]智能车比赛

朴素的做法是 O(n2) 枚举点对然后 O(n) 判断这两点连边是否合法。显然会超时,考虑优化判断的过程。

由于数据已经满足相邻两个矩阵必定左右相邻,所以最优路径必定是从 S 点一直朝T的方向走,这样连边过程就具有单调性。

在某个点朝右(朝左同理)可以观测到的区域显然是以这个点为顶点的角,而且在向右扩展的过程中,这个角的上半边的变化其实是维护一个上凸包,下半边维护一个下凸包。扩展终止在上半边跑到下半边的下边去的时候。

每次扩展的时候要优先扩展竖直方向的两点,才能保证连边的正确性。

对于同起点的向量 a,b ,若 a,b 的叉积 >0 则说明 a b的顺时针方向。

由于边数量的级别是 O(n2) ,所以直接跑 O(n2) 的最短路。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2005;
const double eps=1e-10;
int n,m,ST,EN;
double speed,d[N<<2];
struct P{
    double x,y;
    P(){}
    P(int _x,int _y){x=_x,y=_y;}
    P(double _x,double _y){x=_x,y=_y;}
    P operator +(P t){return P(x+t.x,y+t.y);} 
    P operator -(P t){return P(x-t.x,y-t.y);}
}S,T,U[N<<1],D[N<<1];
struct LINE{
    P p,v;
    LINE(){}
    LINE(P _p,P _v){p=_p,v=_v;}
};
struct A{P p[4];}a[N];
double cross(P a,P b){return a.x*b.y-a.y*b.x;}
double dis(P a,P b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
void adde(int x,int y,double w){if(d[x]+w<d[y])d[y]=d[x]+w;}
void link(P s,int st,int l,int r){
    P up=P(s.x,s.y+1);
    P down=P(s.x,s.y-1);
    for(int i=l;i<=r;++i){
            if(cross(U[i]-s,up-s)+eps>=0){
                up=U[i];
                if(cross(up-s,down-s)-eps>=0)return;
                adde(st,i*2-1,dis(s,U[i]));
            }
            if(cross(D[i]-s,down-s)-eps<=0){
                down=D[i];
                if(cross(up-s,down-s)-eps>=0)return;
                adde(st,i*2,dis(s,D[i]));
            }
        }
}
void link2(){
    P up=P(T.x,T.y+1);
    P down=P(T.x,T.y-1);
    for(int i=m;i;--i){
        if(cross(U[i]-T,up-T)-eps<=0){
            up=U[i];
            if(cross(up-T,down-T)+eps<=0)return;
            adde(i*2-1,EN,dis(U[i],T));
        }
        if(cross(D[i]-T,down-T)+eps>=0){
            down=D[i];
            if(cross(up-T,down-T)+eps<=0)return;
            adde(i*2,EN,dis(D[i],T));
        }
    }
    if(cross(S-T,up)-eps<=0&&cross(S-T,down)+eps>=0)adde(ST,EN,dis(S,T));
}
int main(){
    int i,j,x,y,z,x1,y1,x2,y2;
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        a[i].p[0]=P(x1,y2);
        a[i].p[1]=P(x1,y1);
        a[i].p[2]=P(x2,y2);
        a[i].p[3]=P(x2,y1);
    }
    scanf("%lf%lf%lf%lf%lf",&S.x,&S.y,&T.x,&T.y,&speed);
    if(S.x>T.x)swap(S,T);
    for(i=1;i<=n;++i){
        if(a[i].p[0].x+eps>=S.x&&a[i].p[0].x-eps<=T.x)U[++m]=a[i].p[0],D[m]=a[i].p[1];
        if(a[i].p[2].x+eps>=S.x&&a[i].p[2].x-eps<=T.x)U[++m]=a[i].p[2],D[m]=a[i].p[3];
    }
    ST=m<<1|1;EN=ST+1;
    for(i=1;i<=EN;++i)d[i]=1e9;d[ST]=0;
    link(S,ST,1,m);
    for(i=1;i<=m;++i){
        link(U[i],i*2-1,i,m);
        link(D[i],i*2,i,m);
    } 
    link2();
    printf("%.8f",d[EN]/speed);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值