POJ 1981 Circle and Points(求半径R圆覆盖最多点数及由圆上两点和半径求圆心)

该博客介绍了如何在给定n个二维点坐标的情况下,找到能够覆盖最多点的单位圆。首先分析了两种方法:一是枚举两个点形成单位圆并计算覆盖点数,二是对每个点构建圆并求圆与圆的交集,通过极角排序计算被覆盖最多的弧。最终,第二种方法的时间复杂度为O(N^2 * logN)。

题目:
POJ 1981 Circle and Points
HDU 1077 Catching Fish
题意:
给出n个点的二维点坐标,问单位圆最多能覆盖多少点?
分析:
①:
最优的情况一定是有两个点在圆弧上。先枚举两个点,计算两点在圆弧上的单位圆(一般会有两个)
但是可以统一取一个方向的(也就是AB取一个然后BA取另外一个).然后枚举所有点,计算在这个单位圆内的点的个数。
这样做的时间复杂度是O(n^3).
②:
对每个点以R为半径画圆,对N个圆两两求交。这一步O(N^2)。问题转化为求被覆盖次数最多的弧。
因为如果最优圆覆盖A点那么最优圆一定在以A点为圆心的圆弧上。那么圆弧倍覆盖多少次也就意味着
以这条圆弧为上任意一点为圆心花园能覆盖多少点。
对每一个圆,求其上的每段弧重叠次数。假如A圆与B圆相交。A上[PI/3, PI/2]的区间被B覆盖(PI为圆周率)。
那么对于A圆,我们在PI/3处做一个+1标记,在PI/2处做一个-1标记。
对于[PI*5/3, PI*7/3]这样横跨0点的区间只要在0点处拆成两段即可。
将一个圆上的所有标记排序,从头开始扫描。初始total=0,碰到+1标记给total++,碰到-1标记total–。
扫描过程中total的最大值就是圆上被覆盖最多的弧。求所有圆的total的最大值就是答案。
极角排序需要2*n*logn,总复杂度O(N^2 * logN)

//1264K 1357MS
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cmath>
using namespace std;
const int MAX_N=310;
const double eps=1e-6;
const double R=1.0;//定义覆盖圆半径为R

int T,n;

struct Point{
    double x,y;
}point[MAX_N];

inline double dis(Point a,Point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

inline Point GetCenter(Point a,Point b)
{//获取a,b两点在圆周上的单元圆圆心,单位圆圆心有两个
    struct Point mid,res;
    mid.x=(a.x+b.x)/2,mid.y=(a.y+b.y)/2;//mid是a,b中点坐标
    double angle=atan2(b.y-a.y,b.x-a.x);//angle是直线ab的倾斜角
    double tmp=dis(a,b)/2;//tmp是线段ab长度的一半
    double d=sqrt(1.0-tmp*tmp);//d是ab中点到圆心的距离
    res.x=mid.x-d*sin(angle);//res是直线ab左边的那个圆心
    res.y=mid.y+d*cos(angle);
    //下面的res是直线ab右边的那个圆心
    //res.x=mid.x+d*sin(angle);
    //res.y=mid.y-d*cos(angle);
    return res;
}

int main()
{
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%lf%lf",&point[i].x,&point[i].y);
        }
        int ans=1;//初始化至少能覆盖一个点!!!
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i==j||dis(point[i],point[j])-2*R>eps) continue;
                int cnt=0;
                struct Point center=GetCenter(point[i],point[j]);
                for(int k=0;k<n;k++){
                    if(dis(point[k],center)-R<=eps) cnt++;
                }
                ans=max(ans,cnt);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAX_N=310;
const double PI=acos(-1.0);
const double R=1.0;//定义覆盖圆半径为R

int T,n,total;

struct Point{
    double x,y;
}point[MAX_N];

struct Angle{
    double data;
    int is;
    bool operator < (const Angle& rhs) const {
        return data<rhs.data;
    }
}angle[MAX_N*2];

inline double Dis(Point a,Point b)//计算线段ab的长度
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

inline double God(Point a,Point b)//计算向量ab的极角
{
    double res=atan(fabs((b.y-a.y)/(b.x-a.x)));
    if(b.y<a.y){
        if(b.x<a.x) res+=PI;
        else res=2*PI-res;
    }else {
        if(b.x<a.x) res=PI-res;
    }
    return res;
}

void solve()
{
    int res=1;
    for(int i=0;i<n;i++){
        total=0;
        for(int j=0;j<n;j++){
            if(i==j) continue;
            double dist=Dis(point[i],point[j]);
            if(dist>2*R) continue;
            double base=God(point[i],point[j]);
            double extra=acos(dist/2.0); //计算差角
            angle[total].data=base+extra;
            angle[total++].is=-1;
            angle[total].data=base-extra;
            angle[total++].is=1;
        }
        if(total<=res) continue;
        sort(angle,angle+total);
        int tmp=1;
        for(int j=0;j<total;j++){
            tmp+=angle[j].is;
            res=max(res,tmp);
        }
    }
    printf("%d\n",res);
}

int main()
{
    //freopen("1077in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%lf%lf",&point[i].x,&point[i].y);
        }
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值