POJ-1556题解(附带计算几何模板)

##题意:
现在给你一个卧室的平面图,卧室里有0到18堵墙(垂直于x轴的)
固定起点(0,5),终点(10,5)
在不经过墙的情况下,要求你求出起点到终点的最短路线

##思考:
首先题目很好理解,即在不撞到墙的情况下从求起点到终点的最短路线
首先可以知道,两点之间直线最短,
所以最优路线要么是一条直线(起点终点之间没有墙阻挡)
要么就是经过某些墙的端点的线段拼接而成(为什么是端点?加入线段不经过端点,比如说从两墙之间的空
白穿过,那么线段总可以向上或向下倾斜到墙的端点处,从而长度更小)
可以这样想,线段的起点已经固定(起点不一定是(0,5)),而线段的终点在垂直于x轴的墙所在的直线上某点,由于对称性可知
线段取最小值是线段的终点于起点平行于x轴,如果不能平行(被墙挡住),那么越靠近这条平行轴,线段的长度越短,
而这种最短的极端情况只能在墙的端点处取得。

一开始我的思路是dp,以墙来分层,可知起点到终点间有n层,每层有3面墙,那么通过当前层的某个端点的最短路线
1 要么是当前层上墙的这个端点可以直接到起点的线段长(如果存在)
2 要么是该端点到可以直接到达之前墙上的某端点的线段,加上起点到这线段的距离(即多个线段长度相加)

//其实我个人感觉这个算法在时间复杂度上几乎和暴力求解无异(因为数据非常小,可以暴力过),需要注意的是第二点:
//直接到达非常重要,因为如果单纯枚举每层端点的最小值相加,结果会比最优解大,因为如果当前层和前面2层,3层甚至是很多层的某个墙的端点可以直接相连,那么从这两点之间的最小距离明显是这条直接连线的长度

#具体代码
##主函数实现部分

int n,wall_index;
double dp[200][200];/*dp数组的含义是:dp[i][n]
是起点到第i层的第n个墙的端点的最短距离
每层墙只有4个端点  
*/
Line walls[300][40];
/*
Line 是线的类,具体实现后面的计算几何模板会有
walls[i][n]的意思是第i层墙的第n个墙
*/
Point points[300][40];
/*
Point 是点的类
points[i][n]的意思是第i层墙的第n个端点
*/
Point start_point;//起点
Line temp_path;
bool is_cross(Line ttt,int nn,int index2)//判断ttt这条线是否与第index2层墙到第nn层墙之间的某个墙有交点
//有交点即不能直接相连,且因为线段是从index2层到nn层墙之间,所以只判断这部分的墙的端点
{
    for(int i=index2;i<nn;i++)
    {
        for(int u=0;u<3;u++)
        if(ttt.linecrossseg(walls[i][u])>0) return false;
        //Line 类的函数 linecrossseg是判断线是否与线段wall[i][u]相交
    }
    return true;
}
void get_dis(int pre,int index)//pre是当前墙端点所在的层数,index是当前墙端点所在层的序号
{
    Line temp;
    temp.e=points[pre][index];//temp.e 是线段的终点,temp.s是线段的起点
    temp.s=start_point;
    if(is_cross(temp,pre,0))//如果当前端点能和起点直接相连,那么最短距离就是两点距离
    {
    dp[pre][index]=dis(start_point.x,start_point.y,temp.e.x,temp.e.y);return ;    
    // 求两点距离,并返回
    //dis函数为求两点距离的函数,后面几何模板会有
     }
    for(int u=0;u<pre;u++)//如果不能直接和起点相连,则遍历之前的点寻找从起点到该点的最短距离
    for(int i=0;i<4;i++)
    {
         temp.s=points[u][i];//注意是 s  
         if(is_cross(temp,pre,u+1)) //判断遍历到的某个端点points[u][i] 与当前端点points[pre][index]是否能直接相连
         //能的话就比较此时的距离是否比 dp[pre][index]小,小的话就刷新dp[pre][index]
         dp[pre][index]=min(dp[pre][index],dp[u][i]+
         dis(points[u][i].x,points[u][i].y,
         points[pre][index].x,points[pre][index].y));
    }
}
void get_ans()//从0daon-1遍历墙的端点,并刷新距离最小值
{
    for(int i=0;i<n;i++)
    {    
        for(int u=0;u<4;u++)
               get_dis(i,u);
    }
    get_dis(n,0);//最后将起点作为第n层墙的端点,遍历最后一次
}
int main()
{
    double x,y1,y2,y3,y4;
    start_point.x=0;
    start_point.y=5;
    while(scanf("%d",&n))
    {
        if(n==-1) break;
        wall_index=n;
      for(int i=0;i<30;i++)
      for(int u=0;u<20;u++)
      dp[i][u]=100000;
        //printf("%f))\n",dp[n][0]);
        for(int i=0;i<n;i++)
        {
            scanf("%lf%lf%lf%lf%lf",&x,&y1,&y2,&y3,&y4);
            walls[i][2].s.x=walls[i][2].e.x=
             walls[i][1].s.x= walls[i][1].e.x=
              walls[i][0].s.x= walls[i][0].e.x=x;    
              walls[i][0].s.y=0;
              walls[i][0].e.y=y1;//这部分是walls数组和points数组的数据读入
              
              walls[i][1].s.y=y2;
              walls[i][1].e.y=y3;
              
              walls[i][2].s.y=y4;
              walls[i][2].e.y=10;
              points[i][0]=walls[i][0].e;
              points[i][1]=walls[i][1].s;
              points[i][2]=walls[i][1].e;
              points[i][3]=walls[i][2].s;
        }
        points[n][0].x=10;//将终点设置为第n层墙的第0个端点
        points[n][0].y=5;
        get_ans();
        printf("%.2f\n",dp[n][0]);
        //依据dp数组意思,最优解存在dp[n][0] 即起点到终点的最短距离

    }
}



##所用到的计算几何模板部分
 

// `计算几何模板`
const double eps = 1e-8;
const double pi = acos(-1.0);
const int maxp = 1010;
//`Compares a double to zero`
int sgn(double x){
    if(fabs(x) < eps)return 0;
    if(x < 0)return -1;
    else return 1;
}


double dis(double x1,double y1,double x2,double y2){
 return sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
 }
 
 
 
  //在建立直线时,需要特判两个点是否构成一个线段


//square of a double
inline double sqr(double x){return x*x;}
/*
 * Point
 * Point()               - Empty constructor
 * Point(double _x,double _y)  - constructor
 * input()             - double input
 * output()            - %.2f output
 * operator ==         - compares x and y
 * operator <          - compares first by x, then by y
 * operator -          - return new Point after subtracting curresponging x and y
 * operator ^          - cross product of 2d points
 * operator *          - dot product
 * len()               - gives length from origin
 * len2()              - gives square of length from origin
 * distance(Point p)   - gives distance from p
 * operator + Point b  - returns new Point after adding curresponging x and y
 * operator * double k - returns new Point after multiplieing x and y by k
 * operator / double k - returns new Point after divideing x and y by k
 * rad(Point a,Point b)- returns the angle of Point a and Point b from this Point
 * trunc(double r)     - return Point that if truncated the distance from center to r
 * rotleft()           - returns 90 degree ccw rotated point
 * rotright()          - returns 90 degree cw rotated point
 * rotate(Point p,double angle) - returns Point after rotateing the Point centering at p by angle radian ccw
 */
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x = _x;
        y = _y;
    }
    void input(){
        scanf("%lf%lf",&x,&y);
    }
    void output(){
        printf("%.2f %.2f\n",x,y);
    }
    bool operator == (Point b)const{
        return sgn(x-b.x) == 0 && sgn(y-b.y) == 0;
    }
    bool operator < (Point b)const{
        return sgn(x-b.x)== 0?sgn(y-b.y)<0:x<b.x;
    }
    Point operator -(const Point &b)const{
        return Point(x-b.x,y-b.y);
    }
    //叉积
    double operator ^(const Point &b)const{
        return x*b.y - y*b.x;
    }
    //点积
    double operator *(const Point &b)const{
        return x*b.x + y*b.y;
    }
    //返回长度
    double len(){
        return hypot(x,y);//库函数
    }
    //返回长度的平方
    double len2(){
        return x*x + y*y;
    }
    //返回两点的距离
    double distance(Point p){
        return hypot(x-p.x,y-p.y);
    }
    Point operator +(const Point &b)const{
        return Point(x+b.x,y+b.y);
    }
    Point operator *(const double &k)const{
        return Point(x*k,y*k);
    }
    Point operator /(const double &k)const{
        return Point(x/k,y/k);
    }
    //`计算pa  和  pb 的夹角`
    //`就是求这个点看a,b 所成的夹角`
    //`测试 LightOJ1203`
    double rad(Point a,Point b){
        Point p = *this;
        return fabs(atan2( fabs((a-p)^(b-p)),(a-p)*(b-p) ));
    }
    //`化为长度为r的向量`
    Point trunc(double r){
        double l = len();
        if(!sgn(l))return *this;
        r /= l;
        return Point(x*r,y*r);
    }
    //`逆时针旋转90度`
    Point rotleft(){
        return Point(-y,x);
    }
    //`顺时针旋转90度`
    Point rotright(){
        return Point(y,-x);
    }
    //`绕着p点逆时针旋转angle`
    Point rotate(Point p,double angle){
        Point v = (*this) - p;
        double c = cos(angle), s = sin(angle);
        return Point(p.x + v.x*c - v.y*s,p.y + v.x*s + v.y*c);
    }
};
/*
 * Stores two points
 * Line()                         - Empty constructor
 * Line(Point _s,Point _e)        - Line through _s and _e
 * operator ==                    - checks if two points are same
 * Line(Point p,double angle)     - one end p , another end at angle degree
 * Line(double a,double b,double c) - Line of equation ax + by + c = 0
 * input()                        - inputs s and e
 * adjust()                       - orders in such a way that s < e
 * length()                       - distance of se
 * angle()                        - return 0 <= angle < pi
 * relation(Point p)              - 3 if point is on line
 *                                  1 if point on the left of line
 *                                  2 if point on the right of line
 * pointonseg(double p)           - return true if point on segment
 * parallel(Line v)               - return true if they are parallel
 * segcrossseg(Line v)            - returns 0 if does not intersect
 *                                  returns 1 if non-standard intersection
 *                                  returns 2 if intersects
 * linecrossseg(Line v)           - line and seg
 * linecrossline(Line v)          - 0 if parallel
 *                                  1 if coincides
 *                                  2 if intersects
 * crosspoint(Line v)             - returns intersection point
 * dispointtoline(Point p)        - distance from point p to the line
 * dispointtoseg(Point p)         - distance from p to the segment
 * dissegtoseg(Line v)            - distance of two segment
 * lineprog(Point p)              - returns projected point p on se line
 * symmetrypoint(Point p)         - returns reflection point of p over se
 *
 */
struct Line{
    Point s,e;
    Line(){}
    Line(Point _s,Point _e){
        s = _s;
        e = _e;
    }
    bool operator ==(Line v){
        return (s == v.s)&&(e == v.e);
    }
    //`根据一个点和倾斜角angle确定直线,0<=angle<pi`
    Line(Point p,double angle){
        s = p;
        if(sgn(angle-pi/2) == 0){
            e = (s + Point(0,1));
        }
        else{
            e = (s + Point(1,tan(angle)));
        }
    }
    //ax+by+c=0
    Line(double a,double b,double c){
        if(sgn(a) == 0){
            s = Point(0,-c/b);
            e = Point(1,-c/b);
        }
        else if(sgn(b) == 0){
            s = Point(-c/a,0);
            e = Point(-c/a,1);
        }
        else{
            s = Point(0,-c/b);
            e = Point(1,(-c-a)/b);
        }
    }
    void input(){
        s.input();
        e.input();
    }
    void adjust(){
        if(e < s)swap(s,e);
    }
    //求线段长度
    double length(){
        return s.distance(e);
    }
    //`返回直线倾斜角 0<=angle<pi`
    double angle(){
        double k = atan2(e.y-s.y,e.x-s.x);
        if(sgn(k) < 0)k += pi;
        if(sgn(k-pi) == 0)k -= pi;
        return k;
    }
    //`点和直线关系`
    //`1  在左侧`
    //`2  在右侧`
    //`3  在直线上`
    int relation(Point p){
        int c = sgn((p-s)^(e-s));
        if(c < 0)return 1;
        else if(c > 0)return 2;
        else return 3;//c==0
    }
    // 点在线段上的判断
    bool pointonseg(Point p){
        return sgn((p-s)^(e-s)) == 0 && sgn((p-s)*(p-e)) <= 0;
    }
    //`两向量平行(对应直线平行或重合)`
    bool parallel(Line v){
        return sgn((e-s)^(v.e-v.s)) == 0;
    }
    //`两线段相交判断`
    //`2 规范相交`
    //`1 非规范相交`
    //`0 不相交`
    int segcrossseg(Line v){
        int d1 = sgn((e-s)^(v.s-s));
        int d2 = sgn((e-s)^(v.e-s));
        int d3 = sgn((v.e-v.s)^(s-v.s));
        int d4 = sgn((v.e-v.s)^(e-v.s));
        if( (d1^d2)==-2 && (d3^d4)==-2 )return 2;
        return (d1==0 && sgn((v.s-s)*(v.s-e))<=0) ||
            (d2==0 && sgn((v.e-s)*(v.e-e))<=0) ||
            (d3==0 && sgn((s-v.s)*(s-v.e))<=0) ||
            (d4==0 && sgn((e-v.s)*(e-v.e))<=0);
    }
    //`直线和线段相交判断`
    //`-*this line   -v seg`
    //`2 规范相交`
    //`1 非规范相交`
    //`0 不相交`
    int linecrossseg(Line v){
        int d1 = sgn((e-s)^(v.s-s));
        int d2 = sgn((e-s)^(v.e-s));
        if((d1^d2)==-2) return 2;
        return (d1==0||d2==0);
    }
    //`两直线关系`
    //`0 平行`
    //`1 重合`
    //`2 相交`
    int linecrossline(Line v){
        if((*this).parallel(v))
            return v.relation(s)==3;
        return 2;
    }
    //`求两直线的交点`
    //`要保证两直线不平行或重合`
    //相似三角形证明!
    Point crosspoint(Line v){
        double a1 = (v.e-v.s)^(s-v.s);
        double a2 = (v.e-v.s)^(e-v.s);
        return Point((s.x*a2-e.x*a1)/(a2-a1),(s.y*a2-e.y*a1)/(a2-a1));
    }
    //点到直线的距离
    double dispointtoline(Point p){
        return fabs((p-s)^(e-s))/length();
    }
    //点到线段的距离
    double dispointtoseg(Point p){
        if(sgn((p-s)*(e-s))<0 || sgn((p-e)*(s-e))<0)
            return min(p.distance(s),p.distance(e));
        return dispointtoline(p);
    }
    //`返回线段到线段的距离`
    //`前提是两线段不相交,相交距离就是0了`
    double dissegtoseg(Line v){
        return min(min(dispointtoseg(v.s),dispointtoseg(v.e)),min(v.dispointtoseg(s),v.dispointtoseg(e)));
    }
    //`返回点p在直线上的投影`
    Point lineprog(Point p){
        return s + ( ((e-s)*((e-s)*(p-s)))/((e-s).len2()) );
    }
    //`返回点p关于直线的对称点`
    Point symmetrypoint(Point p){
        Point q = lineprog(p);
        return Point(2*q.x-p.x,2*q.y-p.y);
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值