【洛谷1742】最小圆覆盖(随机增量法)
- 给定\(n\)个点,求这些点的最小覆盖圆。
- \(n\le10^5\)
\(O(n^3)\)的超级暴力
首先我们需要知道一个定理,如果\(P\)不在点集\(S\)的最小覆盖圆上,则\(P\)必然在\(S\cup\{P\}\)的最小覆盖圆上。
而众所周知三点确定一个圆,所以我们就有一个很暴力的做法。
首先我们枚举一个点\(p_i\),如果它不在原本前\(i-1\)个点的最小覆盖圆上,就必然在当前前\(i\)个点的最小覆盖圆上。
因此我们重构最小覆盖圆,由于初始只能确定这一个点在最小覆盖圆上,所以令此时的最小覆盖圆圆心为当前点,半径为\(0\)。
然后我们再在\([1,i)\)中枚举点\(p_j\),如果它不在当前的最小覆盖圆上,同理我们可以确定它在新的最小覆盖圆上,那么我们就能确定两个点。
所以令此时的最小覆盖圆的圆心为这两个点构成线段的中点,半径就是这两点间距离的一半。
最后,再在\([1,j)\)中枚举点\(p_k\),再度同理,如果它不在当前的最小覆盖圆上,它就在新的最小覆盖圆上。
此时我们已经能确定最小覆盖圆上的三个点了,由于三点确定一个圆,所以肯定可以计算出当前的最小覆盖圆。
三点确定一个圆
假设我们已知三点\(A,B,C\),要求出它们的最小覆盖圆。
众所周知,外心是中垂线的交点,所以我们只要求出\(AB,AC\)的中垂线,然后求出它们的交点即可。
中垂线就是取中点作垂线,而求交点可以用面积法。
应该是非常基础的计算几何了吧。
随机增量法
前面说了这么多,但复杂度似乎是非常爆炸的\(O(n^3)\)。
然而,其实我们只要把“良心”的出题人为我们精心构造的数据给\(random\_shuffle\)一发,就能过了!
为什么呢?
依旧是利用三点确定一个圆的结论,那么\(p_i\)不在之前的最小覆盖圆内的概率应该不大于\(\frac 3i\),\(p_j\)不在之前的最小覆盖圆内的概率应该不大于\(\frac 3j\)。
所以说,期望下操作总次数应该是:
也就是说,复杂度应该是非常神奇的\(O(n)\)!
人们常说\(O(n^2)\)过百万,居然没想到还能\(O(n^3)\)过十万。
代码:\(O(n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB double
using namespace std;
int n;struct P
{
DB x,y;I P(Con DB& a=0,Con DB& b=0):x(a),y(b){}
I P operator + (Con P& o) Con {return P(x+o.x,y+o.y);}
I P operator - (Con P& o) Con {return P(x-o.x,y-o.y);}
I P operator * (Con DB& o) Con {return P(x*o,y*o);}
I P operator / (Con DB& o) Con {return P(x/o,y/o);}
I DB operator * (Con P& o) Con {return x*o.x+y*o.y;}
I DB operator ^ (Con P& o) Con {return x*o.y-y*o.x;}
I DB L() {return sqrt((*this)*(*this));}//长度
}p[N+5];
struct Cir
{
P O;DB r;I Cir(Con P& o=P(),Con DB& x=0):O(o),r(x){}
I bool Chk(Con P& p) {return (O-p).L()<=r;}//判断点是否在圆内
}C;
struct S {P A,B;I S(Con P& a=P(),Con P& b=P()):A(a),B(b){}};
I S MidVer(Con P& A,Con P& B) {P mid=(A+B)/2,t=B-mid;return S(mid,mid+P(t.y,-t.x));}//中垂线
I P GetO(Con P& A,Con P& B,Con P& C)//三点确定一个圆
{
S S1=MidVer(A,B),S2=MidVer(A,C);DB w1=(S1.A-S2.A)^(S2.B-S2.A),w2=(S2.B-S2.A)^(S1.B-S2.A);//计算面积
return S1.A+(S1.B-S1.A)*w1/(w1+w2);//求交点
}
int main()
{
RI i,j,k;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);
srand(302627441),random_shuffle(p+1,p+n+1);//随机增量法
for(i=1;i<=n;++i) if(!C.Chk(p[i])) for(C=Cir(p[i],0),//确定一个点
j=1;j^i;++j) if(!C.Chk(p[j])) for(C=Cir((p[i]+p[j])/2,(p[i]-p[j]).L()/2),//确定两个点
k=1;k^j;++k) if(!C.Chk(p[k])) C.O=GetO(p[i],p[j],p[k]),C.r=(p[i]-C.O).L();//确定三个点
return printf("%.10lf\n%.10lf %.10lf\n",C.r,C.O.x,C.O.y),0;//输出答案
}