UVALive 2221 Frontier(计算几何 + DP)

题目大意:有n个tower,m个monument,要求n个点中找出几个点围成一个凸多边形包含这m个点,使这个多边形周长最小,输出这个周长。

思路:m个点全都要被包含,明显只关系到最外围的点,那么把m个点求凸包,然后考虑如果取某个点为起点顺时针走,那么一个一个点加进去,最后这个点可以和前面的点连接,设中间这个点是k,然后d[ i ][ j ]  = min(d[ i ][ k ] + len(k , j) ),其中k、j这段中m没有点在外面,d[ i ][ j ]表示以 i 为起点,j 为终点顺时针走的最短距离。

先开始一直有个地方不会,就是怎么判断这个线没有点在外面,后来发现,只要做三个点叉积就行了,右手比划一下就行了。

这里还有个坑,那就是如果m == 0,那么需要特判,面积不为0,那么此时是个三角形,有可能出现三个点共线的情况,如果 m 有值,就肯定不会取到三角形三点共线的情况。(看 discuss 看来的。。。 = =)

代码如下:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const double INF = 1e9;

const double eps = 1e-8;

struct Point {
    double x, y;
    Point(){}
    Point(double a,double b): x(a),y(b) {}
    void read()
    {
        scanf("%lf%lf",&x,&y);
    }
    Point operator - (const Point& t) const {
        Point tmp;
        tmp.x = x - t.x;
        tmp.y = y - t.y;
        return tmp;
    }
    Point operator + (const Point& t) const {
        Point tmp;
        tmp.x = x + t.x;
        tmp.y = y + t.y;
        return tmp;
    }
    bool operator == (const Point& t) const {
        return fabs(x-t.x) < eps && fabs(y-t.y) < eps;
    }
}tow[55],mon[1111<<1];

inline double Cross(Point a, Point b, Point c) {                    // 叉积
    return (b.x-a.x)*(c.y-a.y) - (c.x-a.x)*(b.y-a.y);
}
inline double PPdis(Point a, Point b) {                             // 点点距离
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline double PLdis(Point p,Point l1,Point l2){                     // 点线距离
    return fabs(Cross(p,l1,l2))/PPdis(l1,l2);
}
inline bool same_dir(Point a, Point b) {                            // 向量是否同向
    return fabs(a.x*b.y-b.x*a.y) < eps && a.x*b.x > -eps && a.y*b.y > -eps;
}
bool dotOnSeg(Point p, Point s, Point e) {                          // 点是否在线段上
    if ( p == s || p == e )     // 看具体情况端点是否合法
        return true;
    return fabs((p-s).x*(p-e).y - (p-e).y*(p-s).x) < eps &&
        (p.x-s.x)*(p.x-e.x)<eps && (p.y-s.y)*(p.y-e.y)<eps;
}
bool Intersect(Point p1, Point p2, Point p3, Point p4, Point& p) {  // 直线相交
    double a1, b1, c1, a2, b2, c2, d;
    a1 = p1.y - p2.y; b1 = p2.x - p1.x; c1 = p1.x*p2.y - p2.x*p1.y;
    a2 = p3.y - p4.y; b2 = p4.x - p3.x; c2 = p3.x*p4.y - p4.x*p3.y;
    d = a1*b2 - a2*b1;
    if ( fabs(d) < eps ) return false;
    p.x = (-c1*b2 + c2*b1) / d;
    p.y = (-a1*c2 + a2*c1) / d;
    return true;
}
bool cmpyx(Point a, Point b) {
    if ( a.y != b.y )
        return a.y < b.y;
    return a.x < b.x;
}
void Grahamxy(Point *p, int &n) {                                   // 水平序(住:两倍空间)
    if ( n < 3 )
        return;
    int i, m=0, top=1;
    sort(p, p+n, cmpyx);
    for (i=n; i < 2*n-1; i++)
        p[i] = p[2*n-2-i];
    for (i=2; i < 2*n-1; i++) {
        while ( top > m && Cross(p[top], p[i], p[top-1]) < eps )
            top--;
        p[++top] = p[i];
        if ( i == n-1 ) m = top;
    }
    n = top;
}

bool is[55][55];

double d[55][55];

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 0;i<n;i++)
            tow[i].read();
        for(int i = 0;i<m;i++)
            mon[i].read();
        double ans = INF;
        if(m == 0)
        {
            for(int i = 0;i<n;i++)
                for(int j = i+1;j<n;j++)
                    for(int k = j+1;k<n;k++)
                    {
                        double tmp = Cross(tow[i],tow[j],tow[k]);
                        if(tmp <= eps && tmp >= -eps) continue;
                        ans = min(ans,PPdis(tow[i],tow[j]) + PPdis(tow[i],tow[k]) + PPdis(tow[j],tow[k]));
                        //printf("i = %d,j = %d,k = %d,ans = %f\n",i,j,k,ans);
                    }
            printf("%.2f\n",ans);
            continue;
        }
        int tot = m;
        Grahamxy(mon,tot);
        //printf("tot = %d\n",tot);
        //for(int i = 0;i<tot;i++)
            //printf("x = %f,y = %f\n",mon[i].x,mon[i].y);
        for(int i = 0;i<n;i++)
            for(int j  = 0;j<n;j++)
                is[i][j] = 1;
        for(int i = 0;i<n;i++)
        {
            for(int j = 0;j<n;j++)
            {
                if(i == j) continue;
                int ok = 1;
                for(int k  = 0;k<tot;k++)
                {
                    if(Cross(tow[i],tow[j],mon[k]) >= 0)
                    {
                        ok = 0;
                        break;
                    }
                }
                if(!ok) is[i][j] = 0;
            }
            is[i][i] = 0;
        }
        for(int i = 0;i<n;i++)
            for(int j = 0;j<n;j++)
                d[i][j] = INF;
        for(int i = 0;i<n;i++)
            for(int j = (i+1)%n;j!=i;j = (j+1)%n)
                if(is[i][j])
                    d[i][j] = PPdis(tow[i],tow[j]);
        for(int begin = 0;begin < n;begin ++)
        {
            for(int end = (begin + 1)%n;;end = (end+1)%n)
            {
                for(int k = (begin + 1)%n; ;k = (k+1)%n)
                {
                    if(is[k][end])
                        d[begin][end] = min(d[begin][end],d[begin][k] + d[k][end]);
                    if(k == end) break;
                }
                //printf("begin = %d,end = %d,dd = %f\n",begin,end,d[begin][end]);
                if(begin == end) break;
            }
        }
        for(int i =0;i<n;i++)
            ans = min(ans,d[i][i]);
        printf("%.2f\n",ans);
    }
    return 0;
}


/*

4 0
0 0
0 3
3 0
2 0

*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值