最小圆覆盖&最小球覆盖 (增量法和模拟退火法)

     这种题几乎一套板子走天下。

     昨天做最小圆覆盖用的都是增量法,压根没看过退火模拟法,因为退火模拟法并不是很稳定。今天看最小球覆盖时发现用到了退火模拟法,于是看了看最小圆覆盖的退火模拟法,用退火板子提交直接AC,所以一并总结了。

有关题目的链接:传送门

最小圆覆盖:

  • 增量法:

最小圆覆盖&最小球覆盖 (增量法和模拟退火法)_第1张图片  

借鉴了其他博主的推导图,方便理解

板子如下:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long lt;
#define eps 1e-6
#define sqr(x) ((x)*(x))

const int maxn=1000010;
int n;
struct point{
	double x,y;
}p[maxn],O;
double R;//半径 

double getd(point a,point b){ //求直径 
	return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));
}
point getO(point p1,point p2,point p3) { //求圆心 
    point res;
    double a=p2.x-p1.x;
	double b=p2.y-p1.y;
	double c=p3.x-p2.x;
	double d=p3.y-p2.y;
    double e=sqr(p2.x)+sqr(p2.y)-sqr(p1.x)-sqr(p1.y);
    double f=sqr(p3.x)+sqr(p3.y)-sqr(p2.x)-sqr(p2.y);
    res.x=(f*b-e*d)/(c*b-a*d)/2.0; 
    res.y=(a*f-e*c)/(a*d-b*c)/2.0;
    return res;
}
void mincir() {
    O=p[1]; 
	R=0;
    for(int i=1;i<=n;++i){
	    if(getd(p[i],O)-R>eps) { //不在圆内 
	        O=p[i]; 
			R=0;
	        for(int j=1;jeps) {//不在圆内 
		            O=(point){(p[i].x+p[j].x)/2.0,(p[i].y+p[j].y)/2.0};
		            R=getd(p[i],p[j])/2.0;
		            for(int k=1;keps) {//不在圆内 
		                O=getO(p[i],p[j],p[k]);  //外接圆 
		                R=getd(p[i],O);
		            }
		        }
		    } 
	    }
	} 
} 

int main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
    scanf("%lf%lf",&p[i].x,&p[i].y);
    random_shuffle(p+1,p+1+n);// random_shuffle()随机打乱函数 首指针 尾指针 
    mincir();
    printf("%.3f",R);
    return 0;
}

 这里介绍一个随机函数 random_shuffle(p,p+n),用法和sort()一样。    这里为什么要用随机函数是为了降低三层for循环O(n^{3})的复杂度,为O(n),至于为什么...推导我也不懂  (挠头

  • 退火模拟法:

板子: (等我懂了退火模拟法的实现再来补这个算法)

#include
#include
#include
#include
#include
using namespace std;
const double eps=1e-8;
struct POINT{
    double x,y,z;
}p[510];
POINT op;//最小圆的圆心
int n;
inline double dist(POINT &a,POINT &b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void solve(){
    double ret,delta=100.0;
    double maxDis,tempDis;
    int i,id;
    while(delta>eps){
        id=0;
        maxDis=dist(op,p[id]);
        for(i=1;imaxDis){
                maxDis=tempDis;
                id=i;
            }
        }
        ret=maxDis;
        op.x+=(p[id].x-op.x)/maxDis*delta;
        op.y+=(p[id].y-op.y)/maxDis*delta;
        delta*=0.98;
    }
    printf("%.3f\n",ret);
}
int main(){
	scanf("%d",&n);
    op.x=op.y=0;
    for(int i=0;i

 

 

最小球覆盖:

  • 退火模拟法

    随机选取一个点作为初始解,然后不断向当前距离它最远的点靠近。

    对于一个点:球心就是这个点,且半径无穷小
    对于两个点:球心就是两点线段的中点,半径就是线段长度的一半
    对于三个点:三点构成的平面必为球的大圆,球心是三角形的外心,半径就是球心到某个点的距离
    对于四个点:若四点共面,则转换到3点共面;若四点不共面,四面体可以唯一确定一个外接球
    对于五个及以上的点:最小球必为某四个点的外接球

板子

#include
#include
#include
#include
#include
using namespace std;
const double eps=1e-5;
struct POINT{
    double x,y,z;
}p[110],op;//N个点
int n;
double dist(POINT &a,POINT &b){//两点距离
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));
}
double solve(){
    double ret,delta=100.0;//温度
    double maxDis,tempDis;
    int i,id;
    while(delta>eps){
        id=0;
        maxDis=dist(op,p[id]);
        for(i=1;imaxDis){
                maxDis=tempDis;
                id=i;
            }
        }
        ret=maxDis;
        op.x+=(p[id].x-op.x)/maxDis*delta;
        op.y+=(p[id].y-op.y)/maxDis*delta;
        op.z+=(p[id].z-op.z)/maxDis*delta;
        delta*=0.98;
    }
    return ret;//最小球半径
}
int main(){
    while(scanf("%d",&n)!=EOF&&n){
        for(int i=0;i

 

这个温度delta值的范围随时间而定,时间给的越多那就开的越大,毕竟会越精确嘛

 

你可能感兴趣的:(计算几何)