poj 1556 简单计算几何+简单图论

/*
题意:
一个正方形房间中,有若干堵墙,墙垂直于x轴且占满y轴方向,每堵墙都有两扇门可通过;
给出门的坐标,求从房间左边中点到房间右边中点的最短距离。(大概题意,有很多误差,
需参照原文理解)

题解:线段相交+dijstra
从一堵墙到另一堵墙的最短距离必定是走直线,如果不能走直线,则其最短路径必定是经过
门的两点,因此需要找出的是所有的墙两两之间的通行点是否能连接成一条线段(不与墙相交),
以此为基础建图,不相交则加边,相交则不能直接到达,最终用dijstra求最短路。

注意:有很多细节的考虑
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

#define Max 105
#define Maxnum 200000000

struct point
{
    double x,y;
};

struct wall
{
    struct point p[4];
}s[Max];

double m[4*Max][4*Max];
double dis[4*Max];
bool vis[4*Max];

//   求叉积
double multiply(point a, point b,point o){
    return (a.x - o.x)*(b.y - o.y) - (b.x - o.x)*(a.y - o.y);
}
// 求点是否在线段上
bool onseg(point a, point b, point o){
    if((multiply(a,b,o) == 0) && 
        ( o.x >= std::min(a.x,b.x) && o.x <= std::max(a.x,b.x) 
        && o.y <= std::max(a.y,b.y) && o.y >= std::min(a.y,b.y)))
        return true;
    return false;
}
// 线段是否相交
int segments_intersert( point p1,point p2,point p3,point p4 )
{
    double d1,d2,d3,d4;
    d1 = multiply ( p1,p2,p3 );
    d2 = multiply ( p1,p2,p4 );
    d3 = multiply ( p3,p4,p1 );
    d4 = multiply ( p3,p4,p2 );
    if( d1*d2<0 && d3*d4<0 )
        return 1;
    else if( d1==0 && onseg( p1,p2,p3 ) )
        return 1;
    else if( d2==0 && onseg( p1,p2,p4 ) )
        return 1;
    else if( d3==0 && onseg( p3,p4,p1 ) )
        return 1;
    else if( d4==0 && onseg( p3,p4,p2 ) )
        return 1;
    return 0;
}

// 求两点间的距离
double dist(point a, point b)
{
    return sqrt((a.x - b.x)*(a.x -b.x) +( a.y - b.y)*(a.y - b.y));
}

//  判断线段a-b是否与墙k~墙y-1有相交
bool has_intersert(struct point a, struct point b, int k, int y)
{
    struct point tmp1,tmp2;
    tmp1.y = 0;
    tmp2.y = 10;
    for(; k<y; k++)
    {
        tmp1.x = s[k].p[0].x;
        tmp2.x = s[k].p[0].x;
        if (segments_intersert(a,b,tmp1,s[k].p[0]))
        {
            return true;
        }
        else if (segments_intersert(a,b,s[k].p[1],s[k].p[2]))
        {
            return true;
        }
        else if(segments_intersert(a,b,s[k].p[3],tmp2))
        {
            return true;
        }
    }
    return false;
}

//  求最短路
void dijkstra(int n)
{
    int k;
    memset(vis,false,sizeof(vis));
    for(int i=0; i<=n; i++)
    {
        dis[i] = m[0][i];
    }
    vis[0]=true;
    for(int i=1; i<=n; i++)
    {
        double tmin = Maxnum;
        for(int j=1; j<=n; j++)
        {
            if (!vis[j] && dis[j] < tmin)
            {
                tmin = dis[j];
                k = j;
            }
        }
        vis[k] = true;
        for(int j=1; j<=n; j++)
            if (dis[k]+m[k][j] < dis[j])
                dis[j] = dis[k] + m[k][j];
    }
}

int main(void)
{
    int n;
    struct point start, end;
    start.x = 0;
    start.y = 5;
    end.x = 10;
    end.y = 5;
    while (~scanf("%d",&n) && n!=-1)
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%lf%lf%lf%lf%lf",&s[i].p[0].x,&s[i].p[0].y,&s[i].p[1].y,&s[i].p[2].y,&s[i].p[3].y);
            s[i].p[1].x = s[i].p[2].x = s[i].p[3].x = s[i].p[0].x;
        }
        for(int i=0; i<=4*n+1; i++)
        {
            for(int j=0; j<=4*n+1; j++)
            {
                m[i][j] = Maxnum;
            }
        }
        if (n == 0)
        {// 没有墙  直接输出结果
            printf("%.2lf\n",dist(start,end));
            continue;
        }
        if (!has_intersert(start,end,1,n+1))
        {// 没有起点与终点可直接相连则直接输出
            printf("%.2lf\n",dist(start,end));
            continue;
        }

        // 建图
        // 起点到其它所有点
        for(int i=0; i<4; i++)
        {
            m[0][i+1] = dist(start,s[1].p[i]);
        }

        for(int i=2; i<=n; i++)
        {
            for(int j=0; j<4; j++)
                if (!has_intersert(start,s[i].p[j],1,i))
                    m[0][(i-1)*4+j+1] = dist(start,s[i].p[j]);
        }

        // 中间墙所有点到终点
        for(int i=0; i<4; i++)
        {
            m[4*(n-1)+i+1][4*n+1] = dist(s[n].p[i],end);
        }

        for(int i=1; i<n; i++)
        {
            for(int j=0; j<4; j++)
                if (!has_intersert(s[i].p[j],end,i+1,n+1))
                    m[4*(i-1)+j+1][4*n+1] = dist(s[i].p[j],end);
        }
        
        // 中间的墙与墙之间可连接的点加边
        for(int i=1; i<n; i++)
        {
            for(int j=i+1; j<=n; j++)
            {
                for(int k=0; k<4; k++)
                {
                    for(int l=0; l<4; l++)
                    {
                        if (i+1 == j)
                        {// 两堵墙相邻
                            m[4*(i-1)+k+1][(j-1)*4+l+1] = dist(s[i].p[k],s[j].p[l]);
                        }
                        else
                        {// 不相邻则判断能否直接到达
                            if (!has_intersert(s[i].p[k],s[j].p[l],i+1,j))
                                m[4*(i-1)+k+1][(j-1)*4+l+1] = dist(s[i].p[k],s[j].p[l]);
                        }
                    }
                }
            }
        }

        dijkstra(n*4+1);
        printf("%.2lf\n",dis[n*4+1]);
    }
    return 0;
}

 

posted @ 2014-03-20 23:25  辛力啤  阅读(233)  评论(0编辑  收藏  举报