这种题几乎一套板子走天下。
昨天做最小圆覆盖用的都是增量法,压根没看过退火模拟法,因为退火模拟法并不是很稳定。今天看最小球覆盖时发现用到了退火模拟法,于是看了看最小圆覆盖的退火模拟法,用退火板子提交直接AC,所以一并总结了。
有关题目的链接:传送门
最小圆覆盖:
- 增量法:
借鉴了其他博主的推导图,方便理解
板子如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<ctime>
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;j<i;j++) {
if(getd(p[j],O)-R>eps) {//不在圆内
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;k<j;++k)
if(getd(p[k],O)-R>eps) {//不在圆内
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()的复杂度,为O(n),至于为什么...推导我也不懂 (挠头
- 退火模拟法:
板子: (等我懂了退火模拟法的实现再来补这个算法)
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
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;i<n;i++){
tempDis=dist(op,p[i]);
if(tempDis>maxDis){
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<n;i++){
scanf("%lf%lf",&p[i].x,&p[i].y);
op.x+=p[i].x;
op.y+=p[i].y;
}
op.x/=n;
op.y/=n;
solve();
return 0;
}
最小球覆盖:
- 退火模拟法
随机选取一个点作为初始解,然后不断向当前距离它最远的点靠近。
对于一个点:球心就是这个点,且半径无穷小
对于两个点:球心就是两点线段的中点,半径就是线段长度的一半
对于三个点:三点构成的平面必为球的大圆,球心是三角形的外心,半径就是球心到某个点的距离
对于四个点:若四点共面,则转换到3点共面;若四点不共面,四面体可以唯一确定一个外接球
对于五个及以上的点:最小球必为某四个点的外接球
板子
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
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;i<n;i++){
tempDis=dist(op,p[i]);
if(tempDis>maxDis){
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<n;i++){
scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].z);
}
printf("%.5f\n", solve());
}
return 0;
}
这个温度delta值的范围随时间而定,时间给的越多那就开的越大,毕竟会越精确嘛