[codevs 1302] 小矮人(2002年CEOI中欧信息学奥赛)

描述

矮人们平时有走亲访友的习惯。一天,矮人国要修一条高速公路,矮人们希望他们走亲访友的时候,能够不必穿越高速公路,这样会更安全一些。现在有M个高速公路的修建方案,请你判断这M条高速功能是否能满足矮人们的期望。也就是说给出平面上的N个点(矮人们的住所位置),对于M条直线(高速公路),依次判断这N个点是否在每条直线的同一侧。是输出GOOD,不是输出BAD。

后来从刘汝佳的书里发现这个算法叫旋转卡壳,专门求对踵点的。


题解

首先可以想出一个凸包模型来,因为求出所有点的凸包后可以判断直线如果穿过凸包,就一定不满足题意。
问题是怎么判断直线是否穿过凸包呢?

如果直线穿过凸包,就一定在凸包两个最远点的中间,最远点可以通过下面的方法找:首先预处理记录下凸包每个边的斜率,再算出直线的斜率,将斜率从小到大排列,假设直线斜率是正的,二分查找第一个大于直线斜率的边,它的起点一定是一个最远点,可以画图验证一下,因为他后面的边斜率比它大,也就相当于走的离凸包中心越来越近。然后把斜率取相反数,再找第一个斜率大于它的边,起点是另一个最远点。

在实现时直接用atan2()函数计算角度不用算斜率。


代码:

总时间耗费: 808ms
总内存耗费: 4 MB

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

const int maxn = 100000 + 10;
const double PI = acos(double(-1));

int n;

struct Point {
    double x, y;
    Point(double x=0, double y=0):x(x),y(y) {}
}p[maxn], ch[maxn];

typedef Point Vector;

Vector operator + (Vector A, Vector B) { return Vector(A.x+B.x, A.y+B.y); }
Vector operator - (Vector A, Vector B) { return Vector(A.x-B.x, A.y-B.y); }
Vector operator * (Vector A, double p) { return Vector(A.x*p, A.y*p); }
Vector operator / (Vector A, double p) { return Vector(A.x/p, A.y/p); }

bool operator < (const Vector& a, const Vector& b) {
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}

const double eps = 1e-10;
int dcmp(double x) {
    if(fabs(x) < eps) return 0; else return x < 0 ? -1 : 1;
}

bool dcmp2(const double& a, const double& b) {
    if(dcmp(b-a) == 1) return 1;
    return 0;
}

bool operator == (const Vector& a, const Vector& b) { 
    return dcmp(a.x-b.x) == 0 && dcmp(a.y-b.y) == 0; 
}

double angle(Vector v) { double ret = atan2(v.y, v.x); return ret < -PI/2 ? ret+2*PI : ret; }
double Cross(Vector A, Vector B) { return A.x*B.y - A.y*B.x; }

int ConvexHull() {
    sort(p, p+n);
    int m = 0;
    for(int i = 0; i < n; i++) {
        while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;
        ch[m++] = p[i];
    }
    int k = m;
    for(int i = n-2; i >= 0; i--) {
        while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;
        ch[m++] = p[i];
    }
    if(n > 1) m--;
    return m;
}

double ang[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) 
        scanf("%lf%lf", &p[i].x, &p[i].y);
    int c = ConvexHull();
    for(int i = 0; i < c; i++) ang[i] = angle(ch[i+1]-ch[i]);

    Point a, b;
    while(scanf("%lf%lf%lf%lf", &a.x, &a.y, &b.x, &b.y) == 4) {
        if(n <= 1) printf("GOOD\n"); 
        else {
            Point u = ch[upper_bound(ang, ang+c, angle(b-a), dcmp2)-ang];
            Point v = ch[upper_bound(ang, ang+c, angle(a-b), dcmp2)-ang];
            if(dcmp(Cross(b-a, u-a)*Cross(b-a, v-a)) < eps) printf("BAD\n");
            else printf("GOOD\n");
        }
    }

    return 0;
}

转载于:https://www.cnblogs.com/wfwbz/p/4282453.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值