P1742 最小圆覆盖 (计算几何)

本文介绍了如何使用随机增量法求解最小圆覆盖问题,期望复杂度为O(n)。首先随机打乱点集,然后通过定理判断点是否在已知覆盖圆内或边界上。通过迭代确定每个点的位置,逐步构建最小覆盖圆。最后提到,随机打乱点集时需要注意避免特定错误情况,并感谢知乎上的O(n)随机数列解决方案。
摘要由CSDN通过智能技术生成

题目链接

题面:
在这里插入图片描述

题解:
求最小圆覆盖。
随机增量法,期望复杂度O(n)。
先将所有的点随机打乱。
定理1: 如果点p不在集合S的最小覆盖圆内,则p一定在S∪{p}的最小覆盖圆上。

根据这个定理,我们可以分三个for确定前i个点的最小覆盖圆。

1.令前i-1个点的最小覆盖圆为C
2.如果第i个点在C内(包括边界),则前i个点的最小覆盖圆也是C
3.如果不在,那么第i个点一定在前i个点的最小覆盖圆上,接着确定前i−1个点中还有哪两个在最小覆盖圆上。因此,设当前圆心为Pi,半径为0,做固定了第i个点的前i个点的最小圆覆盖。
4.固定了一个点–去找第二个点:不停地在范围内找到第一个不在当前最小圆上的点Pj ,当前圆心为(Pi+Pj)/2半径为1/2 | Pi Pj | 做固定了两个点的,前 j 个点外加第 i 个点的最小圆覆盖。
5.固定了两个点–去找第三个点:不停地在范围内找到第一个不在当前最小圆上的点Pk,当前圆为Pi , Pj , Pk 的外接圆。

用random_shuffle随机打乱会wa两个点。。。
真感谢知乎上见到的O(n)随机数列。

从后往前遍历,对于每一个 p [ i ],随机与它前面的一个数交换。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<map>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;

const double eps=1e-8;
const double dnf=1e20;
const double pi=acos(-1.0);
const int maxp=100100;

//浮点型数值是否为0
int sgn(double x)
{
   
    if(abs(x)<eps) return 0;
    if(x<0) return -1;
    return 1;
}


//返回x的平方
double sqr(double x)
{
   
    return x*x;
}

//二维点
struct Point
{
   
    double  x,y;
    Point(){
   }
    Point(double xx,double yy)
    {
   
        x=xx,y=yy;
    }

    //输入输出
    void input(void)
    {
   
        scanf("%lf%lf",&x,&y);
    }
    void output(void)
    {
   
        printf("%.2f %.2f\n",x,y);
    }

    //重载比较运算符
    bool operator == (const Point &b) const
    {
   
        return sgn(x-b.x)==0&&sgn(y-b.y)==0;
    }

    bool operator < (const Point &b) const
    {
   
        if(sgn(x-b.x)!=0) return x<b.x;
        else return sgn(y-b.y)<0;
    }

    //重载加减乘除
    Point operator + (const Point &b) const
    {
   
        return Point(x+b.x,y+b.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);
    }

    //叉乘,叉积
    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(void)
    {
   
        return hypot(x,y);
    }

    //长度的平方
    double len2(void)
    {
   
        return x*x+y*y;
    }

    //两点距离
    double dis(const Point &b) const
    {
   
        return hypot(x-b.x,y-b.y);
    }

    //计算pa,pb的夹角,就是这个点看a,b形成的角度
    double rad(const Point &a,const Point &b)const
    {
   
        Point p=*this;
        return abs(atan2(abs((a-p)^(b-p)),(a-p)*(b-p)));
    }

    //化向量长度为r
    Point turn_to_r(double r)
    {
   
        double l=len();
        if(!sgn(l)) return *this;
        r/=l;
        return Point(x*r,y*r);
    }

    //逆时针转90
    Point turn_left(void)
    {
   
        return Point(-y,x);
    }

    //顺时针转90
    Point turn_right(void)
    {
   
        return Point(y,-x);
    }

    //绕p点逆时针转angle
    Point turn_p_angle(const 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);
    }

};

//****************************************************************
//****************************************************************

struct Line
{
   
    Point s,e;
    Line(){
   }

    //两点
    Line(const Point ss,const Point ee)
    {
   
        s=ss,e=ee;
    }

    //点斜
    Line(const Point p,const 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(void)
    {
   
        s.input();
        e.input();
    }

    void output(void)
    {
   
        s.output();
        e.output();
    }

    //两点应该是由s——>e,
    //由x负向指向x正向。
    void adjust(void)
    {
   
        if(e<s) swap(s,e);
    }

    //判断两线段是否重合
    bool operator == (Line b)
    {
   
        adjust(),b.adjust();
        return (s==b.s)&&(e==b.e);
    }

    //求线段长度
    double len(void)
    {
   
        return s.dis(e);
    }

    //返回直线倾斜角
    //0<=angle<pi
    double angle(void)
    {
   
        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 在左侧 (在s——>e的逆时针侧)
    //2 在右侧 (在s——>e的顺时针侧)
    //3 在直线上
    int relation(const Point &p)
    {
   
        int c=sgn((p-s)^(e-s));
        if(c<0) return 1;
        else if(c>0) return 2;
        else return 3;
    }

    //点是否在线段上
    bool p_is_on_seg(const Point &p)
    {
   
        return sgn((p-s)^(e-s))==0&&sgn((p-s)*(p-e))<=0;
    }

    //两向量平行,对应直线平行或者重合
    bool parallel(const Line &v)
    {
   
        return sgn((e-s)^(v.e-v.s))==0;
    }

    //两线段相交判断
    //2 规范相交
    //1 非规范相交
    //0 不相交
    int seg_cross_seg(const Line &v)
    {
   
        int d1=sgn((e-s)^(v
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最小覆盖问题是一个经典的计算几何问题,可以使用 Welzl 算法(也称作随机增量法)在 O(n) 的时间复杂度内解决。下面是 C# 实现的最小覆盖算法: ```csharp using System; using System.Collections.Generic; namespace MinCircleCover { public class Point { public double x; public double y; public Point(double x, double y) { this.x = x; this.y = y; } } public class Circle { public double x; public double y; public double r; public Circle(double x, double y, double r) { this.x = x; this.y = y; this.r = r; } } public class MinCircleCover { private static Random rand = new Random(); private static bool IsInCircle(Point p, Circle c) { double dx = p.x - c.x; double dy = p.y - c.y; double d = Math.Sqrt(dx * dx + dy * dy); return d <= c.r; } private static Circle MinCircleWithTwoPoints(List<Point> points, Point p, Point q) { double cx = (p.x + q.x) / 2; double cy = (p.y + q.y) / 2; double r = Math.Sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)) / 2; Circle c = new Circle(cx, cy, r); foreach (Point point in points) { if (!IsInCircle(point, c)) { c = MinCircleWithTwoPoints(points, p, point); if (!IsInCircle(point, c)) { c = MinCircleWithTwoPoints(points, q, point); } } } return c; } private static Circle MinCircleWithOnePoint(List<Point> points, Point p) { Circle c = new Circle(p.x, p.y, 0); foreach (Point point in points) { if (!IsInCircle(point, c)) { c = MinCircleWithTwoPoints(points, p, point); } } return c; } private static Circle MinCircle(List<Point> points, List<Point> boundary) { if (boundary.Count == 3) { return new Circle(boundary[0].x, boundary[0].y, Math.Sqrt(Math.Max(Math.Max((boundary[1].x - boundary[0].x) * (boundary[1].x - boundary[0].x) + (boundary[1].y - boundary[0].y) * (boundary[1].y - boundary[0].y), (boundary[2].x - boundary[0].x) * (boundary[2].x - boundary[0].x) + (boundary[2].y - boundary[0].y) * (boundary[2].y - boundary[0].y)), (boundary[2].x - boundary[1].x) * (boundary[2].x - boundary[1].x) + (boundary[2].y - boundary[1].y) * (boundary[2].y - boundary[1].y))) / 2); } else if (boundary.Count == 2 || points.Count == 0) { return MinCircleWithOnePoint(points, boundary[0]); } else { int index = rand.Next(points.Count); Point p = points[index]; points.RemoveAt(index); Circle c = MinCircle(points, boundary); if (!IsInCircle(p, c)) { boundary.Add(p); c = MinCircle(points, boundary); boundary.RemoveAt(boundary.Count - 1); } points.Insert(index, p); return c; } } public static Circle MinCircleCover(List<Point> points) { List<Point> boundary = new List<Point>(); return MinCircle(points, boundary); } } } ``` 其中 Point 和 Circle 类分别代表点和的结构体,MinCircleCover 类实现了最小覆盖算法。算法递归地处理点集和边界点集,每次从点集中随机选择一个点,如果该点在当前最小中,就直接忽略;否则,将该点加入边界点集,并递归处理点集和边界点集,最后返回最小
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值