把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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\)

所以说,期望下操作总次数应该是:

\[\sum_{i=1}^n(\frac3i\times (\sum_{j=1}^i\frac3j\times j))=9n \]

也就是说,复杂度应该是非常神奇的\(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;//输出答案
}
posted @ 2021-03-25 13:59  TheLostWeak  阅读(112)  评论(0编辑  收藏  举报