【洛谷1742】最小圆覆盖(随机增量法)

点此看题面

  • 给定(n)个点,求这些点的最小覆盖圆。
  • (nle10^5)

(O(n^3))的超级暴力

首先我们需要知道一个定理,如果(P)不在点集(S)的最小覆盖圆上,则(P)必然在(Scup{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 imes (sum_{j=1}^ifrac3j imes 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
%.10lf %.10lf
",C.r,C.O.x,C.O.y),0;//输出答案
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu1742.html