[BZOJ 2433][NOI 2011]智能车比赛(计算几何+动态规划)

142 篇文章 0 订阅
59 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2433

思路

一个很显然的 O(n3) 做法:每个矩形上有四个顶点,首先暴力枚举定点对 (i,j) ,再枚举其他的点,叉积判断是否有矩形与矩形的连接处卡住了直线 ij ,若没有被卡,那么在图中连 i j的无向边,边权为两点间的距离。然后随便用个什么最短路算法求出起点到终点的最短路就可以得到答案。

这个方法非常丽洁,可惜过不了这道题。上面的做法中判断直线 i,j 是否被卡,很多时候是在做重复劳动,更好的做法是用DP,用 f[i] 表示从起点 S 到点i的最短距离,可以得到下面的一个DP方程

f[i]=min线ij{f[j]+dist(j,i)}

显然是从最左边的矩形DP到最右边的矩形。我们枚举矩形 t 里的点i,然后枚举在 i 后面的点j,用 f[i] 去更新(松弛?) f[j] ,这个过程表示为 update(i,t,f[i]) 。那么在DP过程中,我们维护合法线段 ij 里端点 j 的范围。这个范围可以用两个向量iUp,iDn表示,如下图。
这里写图片描述
当用矩形 t 里的点i f[i] 更新完矩形 k 上在k左侧或中间的点的 f 值后(注意这里,必须在更新这个矩形右侧的点的f值前更新合法区域,原理很简单),则需要用矩形 k 和矩形k+1相邻的交界处去更新三角形的合法区域。用叉积判断新的交界处是否在合法区域内。

但是要加上一个特判,如下面的情况
这里写图片描述
用红色的点去更新蓝色的点的DP值,然后直接退出。因为对于红色的点右边矩形中的所有点(蓝色点除外),与红色点的连线都是不合法的,因此后面就没有必要更新了,可以直接退出。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cmath>

#define MAXE 10000
#define MAXV 51000

using namespace std;

int n;
double f[MAXV];

struct Point
{
    int x,y;
    Point(){}
    Point(int _x,int _y):x(_x),y(_y){}
}points[MAXV],S,T;

Point operator-(Point a,Point b)
{
    return Point(a.x-b.x,a.y-b.y);
}

int cross(Point a,Point b)
{
    return a.x*b.y-a.y*b.x;
}

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

bool inRange(Point a,Point up,Point dn,Point d) //判断d是否在a->b,a->c围成的夹角里
{
    if(cross(up-a,d-a)>0||cross(dn-a,d-a)<0) return false;
    return true;
}

struct Square
{
    Point zx,zs,yx,ys;
    Square(){}
    Square(Point zx,Point zs,Point yx,Point ys):zx(zx),zs(zs),yx(yx),ys(ys){}
}sqr[MAXV];

bool inSquare(Square a,Point b) //判断点b是否在矩形a内
{
    return b.x>=a.zx.x&&b.y>=a.zx.y&&b.x<=a.ys.x&&b.y<=a.ys.y;
}

double ans=1e20;

void update(Point p,int pos,double val) //用矩形pos的点p的f值val去更新这个矩形后面的矩形的每个点的f值
{
    Point up=Point(p.x,p.y+1);
    Point dn=Point(p.x,p.y-1);
    for(int i=pos;i<=n;i++)
    {
        if(inRange(p,up,dn,sqr[i].zx))
            f[i*4]=min(f[i*4],val+dist(p,sqr[i].zx));
        if(inRange(p,up,dn,sqr[i].zs))
            f[i*4+1]=min(f[i*4+1],val+dist(p,sqr[i].zs));
        if(inRange(p,up,dn,sqr[i].yx))
            f[i*4+2]=min(f[i*4+2],val+dist(p,sqr[i].yx));
        if(inRange(p,up,dn,sqr[i].ys))
            f[i*4+3]=min(f[i*4+3],val+dist(p,sqr[i].ys));
        if(inSquare(sqr[i],T)&&inRange(p,up,dn,T))
            ans=min(ans,val+dist(p,T));
        if(i+1<=n) //更新三角区域范围
        {
            Point lowerBound=Point(sqr[i].yx.x,max(sqr[i].yx.y,sqr[i+1].zx.y));
            Point upperBound=Point(sqr[i].yx.x,min(sqr[i].ys.y,sqr[i+1].zs.y)); //矩形i与i+1相连部分是一条竖着的直线lowerBound-upperBound
            if(p.x==sqr[i].yx.x)
            {
                if(lowerBound.y>p.y||upperBound.y<p.y)
                {
                    f[(i+1)*4]=min(f[(i+1)*4],val+dist(p,sqr[i+1].zx));
                    f[(i+1)*4+1]=min(f[(i+1)*4+1],val+dist(p,sqr[i+1].zs));
                    return;
                }
            }
            else //点p是在当前矩形的左侧,那么就要用矩形i与i+1的交界线段去更新三角形的可行区域
            {
                if(cross(dn-p,lowerBound-p)>0) dn=lowerBound;
                if(cross(up-p,upperBound-p)<0) up=upperBound;
                if(cross(up-p,dn-p)>0) return; //已经没有可行区域了
            }
        }
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int zxx,zxy,ysx,ysy;
        scanf("%d%d%d%d",&zxx,&zxy,&ysx,&ysy);
        sqr[i].zx=Point(zxx,zxy);
        sqr[i].zs=Point(zxx,ysy);
        sqr[i].yx=Point(ysx,zxy);
        sqr[i].ys=Point(ysx,ysy);
    }
    scanf("%d%d",&S.x,&S.y);
    scanf("%d%d",&T.x,&T.y);
    if(S.x>T.x) swap(S,T);
    for(int i=0;i<=4*n+4;i++) f[i]=1e20;
    for(int i=1;i<=n;i++) //!!!!!
    {
        if(inSquare(sqr[i],S)) update(S,i,0);
        update(sqr[i].zx,i,f[i*4]);
        update(sqr[i].zs,i,f[i*4+1]);
        update(sqr[i].yx,i,f[i*4+2]);
        update(sqr[i].ys,i,f[i*4+3]);
    }
    double speed;
    scanf("%lf",&speed);
    printf("%lf\n",ans/speed);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值