【SHTSC2014】信号增幅仪

题目大意

给定平面直角坐标系中的n个点,有一个在某一特定的方向上伸长p倍的椭圆。问如何定下将椭圆的圆心,使所有点被覆盖?求这个椭圆的最小半短轴。

题解

引入

题目有两个难点:①这个椭圆在某一个特定的方向伸长。②这是个椭圆,且半长轴的长度为半短轴的p倍。
如果是一个正圆,那么很好求最小圆覆盖。那么能不能够利用题目给的条件来转换成最小圆覆盖问题呢?
这需要我们掌握图形变换的一些常识
考虑一个任意的图形,如果图形上的所有点的横坐标扩大p倍,那么整个图形横向拉长p倍。
如果一个点x与原点O的连线l与x轴的方位角为α,那么将图逆时针旋转α后,x在新的x轴上。
根据以上两点,将n个点顺时针旋转α,横坐标除以p,就转化为最小圆覆盖问题了。

那么最小圆覆盖问题怎么做?

使用随机增量法。
也就是说,通过让圆的半径不断变大而使所有点被圆覆盖。
①随机地加入某一个点i,如果这个点在1~i-1的最小覆盖圆内/上,那么不管。
②如果在圆外,那么使i在新的圆上。
再寻找一个点j,如果j在圆外,那么圆必经点j,那么新圆就是以ij为直径的圆,现在要把前j-1个点加入回这个圆中。否则不管。
如果点k在当前的最小圆外,说明k一定在包括i,j和前k个点的最小覆盖圆上,我们可以直接得出这个圆了。否则不管。
③当点完全随机时,第i个点在圆外的概率为3/i,所以可以证明此算法的期望复杂度为 O(n)
这说明跑这个算法的时候,点必须随机。

常识

旋转公式:将 (x,y) 逆时针旋转 α 得到(x,y)
x=xcos(α)ysin(α)
y=xsin(α)+ycos(α)
三点定圆:已知点 (x1,y1),(x2,y2),(x3,y3) ,求圆心 (x,y)
根据圆心到顶点的距离相等,可列出:
(x1x)(x1x)+(y1y)(y1y)=(x2x)(x2x)+(y2y)(y2y)
(x2x)(x2x)+(y2y)(y2y)=(x3x)(x3x)+(y3y)(y3y)
化简,得:
2x(x2x1)+2y(y2y1)=x22+y22x21y21
2x(x3x2)+2y(y3y2)=x23+y23x22y22
我们需要用到已知的来推未知的,所以
a1=2(x2x1),b1=2(y2y1),c1=x22+y22x21y21
   a2=2(x3x2),b2=2(y3y2),c2=x23+y23x22y22
a1x+b1y=c1,a2x+b2y=c2
解这个二元一次方程组,得
x=c1b2c2b1a1b2a2b1,y=a1c2a2c1a1b2a2b1

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50010
#define DB double
#define eps 1e-9
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    DB x,y;
};note p[N],c;
int i,n;
DB r,ang,pw,X,Y,pi;
DB sgn(DB x){
    if(fabs(x)<eps)return 0;
    return x>0?1:-1; 
}
DB dis(note A,note B){
    return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)); 
}
note new_circle(note A,note B,note C){
    DB a1,a2,b1,b2,c1,c2;
    a1=2*(B.x-A.x);
    b1=2*(B.y-A.y);
    c1=B.x*B.x+B.y*B.y-A.x*A.x-A.y*A.y;
    a2=2*(C.x-B.x);
    b2=2*(C.y-B.y);
    c2=C.x*C.x+C.y*C.y-B.x*B.x-B.y*B.y;
    note circle;
    circle.x=(c1*b2-c2*b1)/(a1*b2-a2*b1);
    circle.y=(a1*c2-a2*c1)/(a1*b2-a2*b1);
    return circle;
}
void min_circle(){
    int i,j,k;
    c.x=c.y=r=0;
    fo(i,2,n)
        if(sgn(dis(p[i],c)-r)>0){
            c=p[i];
            r=0;
            fo(j,1,i-1)
                if(sgn(dis(p[j],c)-r)>0){
                    c.x=(p[i].x+p[j].x)/2.0;
                    c.y=(p[i].y+p[j].y)/2.0;
                    r=dis(p[i],p[j])/2.0;
                    fo(k,1,j-1)
                        if(sgn(dis(p[k],c)-r)>0){
                            c=new_circle(p[i],p[j],p[k]);
                            r=dis(p[i],c);
                        }
                }
        }
}
int main(){
    scanf("%d",&n);
    fo(i,1,n)scanf("%lf%lf",&p[i].x,&p[i].y);
    scanf("%lf%lf",&ang,&pw);
    pi=acos(-1); 
    ang=(360-ang)*pi/180.0;
    fo(i,1,n){
        X=p[i].x*cos(ang)-p[i].y*sin(ang);
        Y=p[i].x*sin(ang)+p[i].y*cos(ang);
        p[i].x=X/(DB)pw;
        p[i].y=Y;
    }
    random_shuffle(p+1,p+n+1);
    min_circle();
    printf("%.3lf",r);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值