hdu3007(最小覆盖圆问题)

转载自ACdreamers

http://acm.hdu.edu.cn/showproblem.php?pid=3007


最小圆覆盖,很经典的问题。

题目大概是,平面上n个点,求一个半径最小的圆,能够覆盖所有的点。

 

如果要求一个最小覆盖圆,这个圆至少要由三个点确定。有一种算法就是任意取三个点作圆,然后判断距离圆心最远的点是否在圆

内,若在,则完成;若不在则用最远点更新这个圆。这里不仔细介绍。

 

这里介绍的算法是,先任意选取两个点,以这两个点的连线为直径作圆。再以此判断剩余的点,看它们是否都在圆内(或圆上),

如果都在,说明这个圆已经找到。如果没有都在:假设我们用的最开始的两个点为p[1],p[2],并且找到的第一个不在圆内(或圆

上)的点为p[i],于是我们用这个点p[i]去寻找覆盖p[1]到p[i-1]的最小覆盖圆。

 

那么,过确定点p[i]的从p[1]到p[i-1]的最小覆盖圆应该如何求呢?

我们先用p[1]和p[i]做圆,再从2到i-1判断是否有点不在这个圆上,如果都在,则说明已经找到覆盖1到i-1的圆。如果没有都

在:假设我们找到第一个不在这个圆上的点为p[j],于是我们用两个已知点p[j]与p[i]去找覆盖1到j-1的最小覆盖圆。

而对于两个已知点p[j]与p[i]求最小覆盖圆,只要从1到j-1中,第k个点求过p[k],p[j],p[i]三个点的圆,再判断k+1到j-1

是否都在圆上,若都在,说明找到圆;若有不在的,则再用新的点p[k]更新圆即可。

于是,这个问题就被转化为若干个子问题来求解了。

由于三个点确定一个圆,我们的过程大致上做的是从没有确定点,到有一个确定点,再到有两个确定点,再到有三个确定点来求圆的工作。

关于正确性的证明以及复杂度的计算这里就不介绍了,可以去看完整的算法介绍:恩。关于细节方面。

a.通过三个点如何求圆?

   先求叉积。

   若叉积为0,即三个点在同一直线,那么找到距离最远的一对点,以它们的连线为直径做圆即可;

   若叉积不为0,即三个点不共线,那么就是第二个问题,如何求三角形的外接圆?

b.如何求三角形外接圆?

   假设三个点(x1,y1),(x2,y2),(x3,y3);

   设过(x1,y1),(x2,y2)的直线l1方程为Ax+By=C,它的中点为(midx,midy)=((x1+x2)/2,(y1+y2)/2),l1中垂线方程为A1x+B1y=C1;则它的中垂线方程中A1=-B=x2-x1,B1=A=y2-y1,C1=-B*midx+A*midy=((x2^2-x1^2)+(y2^2-y1^2))/2;

   同理可以知道过(x1,y1),(x3,y3)的直线的中垂线的方程。

   于是这两条中垂线的交点就是圆心。

c.如何求两条直线交点?

   设两条直线为A1x+B1y=C1和A2x+B2y=C2。

   设一个变量det=A1*B2-A2*B1;

   如果det=0,说明两直线平行;若不等于0,则求交点:x=(B2*C1 -B1*C2)/det,y=(A1*C2-A2*C1)/det;



#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>

using namespace std;

struct Point
{
    double x,y;
};

Point a[1005],d;
double r;

double dist(Point A,Point B)
{
    return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
}

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); //叉乘的矩阵形式
}

void MiniDiscWith2Point(Point p,Point q,int n)
{
    d.x=(p.x+q.x)/2.0;
    d.y=(p.y+q.y)/2.0;
    r=dist(p,q)/2;
    int k;
    double c1,c2,t1,t2,t3;
    for(k=1;k<=n;k++)
    {
        if(dist(d,a[k])<=r) continue;
        if(cross(p,q,a[k])!=0.0)//不平行
        {
            c1=(p.x*p.x+p.y*p.y-q.x*q.x-q.y*q.y)/2.0;
            c2=(p.x*p.x+p.y*p.y-a[k].x*a[k].x-a[k].y*a[k].y)/2.0;
            d.x=(c1*(p.y-a[k].y)-c2*(p.y-q.y))/((p.x-q.x)*(p.y-a[k].y)-(p.x-a[k].x)*(p.y-q.y)); // 两条中垂线的交点,即圆心
            d.y=(c1*(p.x-a[k].x)-c2*(p.x-q.x))/((p.y-q.y)*(p.x-a[k].x)-(p.y-a[k].y)*(p.x-q.x)); // 两条中垂线的交点,即圆心
            r=dist(d,a[k]); // 半径,其实用p,q也可以求出圆心。
        }
        else
        {
            t1=dist(p,q);
            t2=dist(q,a[k]);
            t3=dist(p,a[k]);
            if(t1>=t2&&t1>=t3)
            {
                d.x=(p.x+q.x)/2.0;
                d.y=(p.y+q.y)/2.0;
                r=dist(p,q)/2.0;
            }
            else if(t2>=t1&&t2>=t3)
            {
                d.x=(a[k].x+q.x)/2.0;
                d.y=(a[k].y+q.y)/2.0;
                r=dist(a[k],q)/2.0;
            }
            else
            {
                d.x=(a[k].x+p.x)/2.0;
                d.y=(a[k].y+p.y)/2.0;
                r=dist(a[k],p)/2.0;
            }
        }
    }
}

void MiniDiscWithPoint(Point pi,int n) 
{
    d.x=(pi.x+a[1].x)/2.0;
    d.y=(pi.y+a[1].y)/2.0;
    r=dist(pi,a[1])/2.0;
    int j;
    for(j=2;j<=n;j++)
    {
        if(dist(d,a[j])<=r) continue;
        else
        {
            MiniDiscWith2Point(pi,a[j],j-1);//pi和a[j]都是调皮的点
        }
    }
}

int main()
{
    int i,n;
    while(scanf("%d",&n)&&n)
    {
        for(i=1;i<=n;i++)
            scanf("%lf %lf",&a[i].x,&a[i].y);
        if(n==1)
        {
            printf("%.2lf %.2lf 0.00\n",a[1].x,a[1].y);
            continue;
        }
        r=dist(a[1],a[2])/2.0;
        d.x=(a[1].x+a[2].x)/2.0;
        d.y=(a[1].y+a[2].y)/2.0;
        for(i=3;i<=n;i++)
        {
            if(dist(d,a[i])<=r)continue;
            else MiniDiscWithPoint(a[i],i-1);  // a[i]是超过半径的点,和它的下标i-1一起作为参数
        }
        printf("%.2lf %.2lf %.2lf\n",d.x,d.y,r);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值