凸包最远点对距离应用

应用一

问题描述:平面上有N个点,它们有各自的移动向量,找出在哪一时刻,最远距离的点对距离最短。

输入描述:所有数据都是浮点型,先输入N(2 <= N <= 1000),再输入N个点,初始位置和移动速度向量。

2

0 0 1 0

2 0 -1 0

4

27 27 0 2

58 88 -8 -1

-22 7 1 -1

-38 -26 5 9

输出描述:输出对应时刻和最短距离,保留小数后两位。

1.00 0.00

8.89 81.00


[分析]在N个点中,找出最远点对,引出凸包算法,但是如何描述每个时刻的状态呢(如果是用枚举算法,何时结束计算呢),很难想,因此只能通过三分查找法确定这个时间。所以尝试下凸包+三分法试试。

/*旋转卡壳,求最长点对*/
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn =  300 + 5;
const double eps = 0.000001;
typedef double type_xy;


struct Point {
    type_xy x, y, vx, vy, sx, sy;
    Point() {}
    Point(type_xy x, type_xy y) : x(x), y(y) {}
    Point operator + (Point p){ return Point(x + p.x, y + p.y); }
    Point operator - (Point p){ return Point(x - p.x, y - p.y); }
    Point operator * (type_xy d){ return Point(x*d, y*d); }
    bool operator < (const Point& a) const {
        if (fabs(x - a.x) > eps) return x < a.x;//!=
        else return y < a.y;
    }
    type_xy dot(Point p) { return x*p.x + y*p.y; }
    type_xy det(Point p) { return x*p.y - y*p.x; }
    void translate(double time_) { 
        x = time_ * vx + sx;
        y = time_ * vy + sy;
    }
};

int N;
Point ps[maxn];

//字典序比较
bool cmp_x(const Point& p, const Point& q) {
    if (fabs(p.x - q.x) > eps)
        return p.x < q.x;
    return p.y < q.y;
}

//求凸包
vector<Point> convex_hull(Point* ps, int n) {
    sort(ps, ps + n, cmp_x);
    int k = 0;          //凸包的顶点数
    vector<Point> qs(n * 2);        //构造中的凸包
    //构造凸包的下侧
    for (int i = 0; i < n; i++){
        while (k > 1 && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
            k--;
        qs[k++] = ps[i];
    }
    //构造凸包的上侧
    for (int i = n - 2, t = k; i >= 0; i--){
        while (k > t && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
            k--;
        qs[k++] = ps[i];
    }
    qs.resize(k - 1);
    return qs;
}

//距离的平方
double dist(Point p, Point q) {
    return (p - q).dot(p - q);
}

double solve() {
    vector<Point> qs = convex_hull(ps, N);
    int n = qs.size();
    if (n == 2){         //特别处理凸包退化的情况
        //printf("%.0f\n", );
        return dist(qs[0], qs[1]);
    }
    int i = 0, j = 0;           //某个方向上的对踵点对
    //求出x轴方向上的对踵点对
    for (int k = 0; k < n; ++k){
        if (!cmp_x(qs[i], qs[k]))
            i = k;
        if (cmp_x(qs[j], qs[k]))
            j = k;
    }
    double res = 0;
    int si = i, sj = j;
    while (i != sj || j != si){     //将方向逐步旋转180度
        res = max(res, dist(qs[i], qs[j]));
        //判断先转到边i-(i+1)的法线方向还是边j-(j+1)的法线方向
        if ((qs[(i + 1) % n] - qs[i]).det(qs[(j + 1) % n] - qs[j]) < 0)
            i = (i + 1) % n;        //先转到边i-(i+1)的法线方向
        else
            j = (j + 1) % n;        //先转到边j-(j+1)的法线方向
    }
    //printf("%.0f\n", res);
    return res;
}

double get_max_dis(double time_) {
    for (int i = 0; i < N; ++i) {
        ps[i].translate(time_);
    }
    return solve();
}

int main() {
    //freopen("data/longestpoint.txt","r",stdin);
    while (scanf("%d", &N) != EOF){
        for (int i = 0; i < N; i++){
            scanf("%lf%lf%lf%lf", &ps[i].x, &ps[i].y, &ps[i].vx, &ps[i].vy);
            ps[i].sx = ps[i].x;
            ps[i].sy = ps[i].y;
        }

        double l = 0, r = 1000000;
        double res_dis = solve();
        double res_time = 0;
        while (l < r - eps) {
            double mid = (l + r) * 0.5;
            double mid_mid = (l + mid) * 0.5;

            double res1 = get_max_dis(mid);
            double res2 = get_max_dis(mid_mid);

            if (res1 < res2) {
                l = mid_mid;
                if (res1 < res_dis) {
                    res_dis = res1;
                    res_time = mid;
                }
            } else {
                r = mid;
                if (res2 < res_dis) {
                    res_dis = res2;
                    res_time = mid_mid;
                }
            }
        }
        printf("%.2lf %.2lf\n", res_time, sqrt(res_dis));
    }
    return 0;
}


应用二

问题描述:一个圆上有N个点,按照角度从小到大输入这些点(假设以y轴正方向为起始方向,顺时针旋转),求最大的弧度点对所对应的角度。

输入描述:先输入n表示有n个点(2<= n <=1e8),接下来n行,每一行是一个精确度为8位的浮点数,表示角度。

4

10.00000000

180.00000000

183.000000

198.000000

输出描述:输出一个浮点数,即最大弧所对应的角度。

173.000000

[分析]:同样暴力,直接枚举每个点对,还是会超时。能否假设圆的半径为1,这样可以把角度转化为(x, y)坐标点,同时问题变为求最远点对(最大弦长))所对应的角度。这样做,主要是因为旋转卡壳求最远点对的时间复杂度由排序决定:O(nlogn),而在求最远点对时,都是线性时间:O(n)

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn =  1e8 + 5;
const double eps = 0.000000000000001;
#define PI  3.14159265358979323846//3.14159265358979
typedef double type_xy;

struct Point
{
    type_xy x, y, angle;
    Point() {}
    Point(type_xy x, type_xy y) : x(x), y(y) {}
    Point operator + (Point p){ return Point(x + p.x, y + p.y); }
    Point operator - (Point p){ return Point(x - p.x, y - p.y); }
    Point operator * (type_xy d){ return Point(x*d, y*d); }
    bool operator < (const Point& a) const
    {
        if (fabs(x - a.x) > eps) return x < a.x;//!=
        else return y < a.y;
    }
    type_xy dot(Point p) { return x*p.x + y*p.y; }
    type_xy det(Point p) { return x*p.y - y*p.x; }
    
};

int N;
Point ps[maxn];

//字典序比较
bool cmp_x(const Point& p, const Point& q) {
    if (fabs(p.x - q.x) > eps)
        return p.x < q.x;
    return p.y < q.y;
}

//求凸包
vector<Point> convex_hull(Point* ps, int n) {
    sort(ps, ps + n, cmp_x);
    int k = 0;          //凸包的顶点数
    vector<Point> qs(n * 2);        //构造中的凸包
    //构造凸包的下侧
    for (int i = 0; i < n; i++) {
        while (k > 1 && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
            k--;
        qs[k++] = ps[i];
    }
    //构造凸包的上侧
    for (int i = n - 2, t = k; i >= 0; i--) {
        while (k > t && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
            k--;
        qs[k++] = ps[i];
    }
    qs.resize(k - 1);
    return qs;
}

//距离的平方
double dist(Point p, Point q) {
    return (p - q).dot(p - q);
}

double solve() {
    vector<Point> qs = convex_hull(ps, N);
    int n = qs.size();
    if (n == 2){         //特别处理凸包退化的情况
        //printf("%.0f\n", );
        return dist(qs[0], qs[1]);
    }
    int i = 0, j = 0;           //某个方向上的对踵点对
    //求出x轴方向上的对踵点对
    for (int k = 0; k < n; ++k) {
        if (!cmp_x(qs[i], qs[k]))
            i = k;
        if (cmp_x(qs[j], qs[k]))
            j = k;
    }
    double res = 0;
    int si = i, sj = j;
    while (i != sj || j != si) {     //将方向逐步旋转180度
        res = max(res, dist(qs[i], qs[j]));
        //判断先转到边i-(i+1)的法线方向还是边j-(j+1)的法线方向
        if ((qs[(i + 1) % n] - qs[i]).det(qs[(j + 1) % n] - qs[j]) < 0)
            i = (i + 1) % n;        //先转到边i-(i+1)的法线方向
        else
            j = (j + 1) % n;        //先转到边j-(j+1)的法线方向
    }
    //printf("%.0f\n", res);
    return res;
}

int main() {
    //freopen("data/sogou.txt","r",stdin);

    double angle;
    while (scanf("%d", &N) != EOF) {
        for (int i = 0; i < N; i++) {
            scanf("%lf", &angle);
            ps[i].x = sin(angle * PI / 180.0);//假设半径为1,根据角度求坐标。
            ps[i].y = cos(angle * PI / 180.0);
            ps[i].angle = angle;
        }
        double res = sqrt(solve());//求出最远点对距离(即,弦长)

        printf("%.8lf\n", asin(res/2.0) * 360.0 / PI);
    }
    return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值