题意:有n个楼,给定xy坐标和高度,有一个防护盾核心,以他为球心的半球内的都处于他的保护之下,求他的坐标,使得他需要覆盖全部楼的半径最小。
题解:
模拟退火。
第一个模拟退火,哈哈。
传统的模拟退火如果当前状态更有就接受,否则以一定概率接受,概率的存在保证不会跳进局部最优而出不来。
不过用概率搞貌似效果不是很好,然后就初始生成100组解,对着100组并行操作,只在更优的时候接受,这样每组解最后都会到达他所在区域的局部最优解,而全局最优解很有可能就在这些局部最优解之中,对这100组取个最优作为全局最优值。
这个方法在数据不是特别针对的情况下效果还不错,特别怕那种锯齿形的有很多局部最优解的数据。
#include<stdio.h>
#include<memory.h>
#include<iostream>
#include<algorithm>
#include <stdlib.h>
#include <time.h>
#include<cmath>
using namespace std;
#define TN 180
int n;
struct pt
{
double x,y,h;
};
struct status
{
pt p;
double e,r;
};
pt a[105];
double calc_r(pt a,pt b)
{
return sqrt( (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y) +a.h*a.h );
}
double max_r(pt x)
{
int i;
double ans=0,t;
for(i=1;i<=n;i++)
if ((t=calc_r(a[i],x))>ans)
ans=t;
return ans;
}
status s0[2000],s[2000];
double t0;
double P(double se,double ee)
{
return exp(-(ee-se)/ee);
}
double fmax(double x,double y)
{
if (x>y) return x;
else return y;
}
int main()
{
int i,j,k,u;
double minx,miny,maxx,maxy;
srand(time(NULL));
while(cin>>n)
{
if (n==0) break;
minx=10000000;maxx=-100000000;miny=100000000;maxy=-10000000;
for(i=1;i<=n;i++)
{
cin>>a[i].x>>a[i].y>>a[i].h;
if (a[i].x<minx) minx=a[i].x;
if (a[i].x>maxx) maxx=a[i].x;
if (a[i].y<miny) miny=a[i].y;
if (a[i].y>maxy) maxy=a[i].y;
}
for(i=0;i<=TN;i++)
{
s0[i].p.x=minx+i*(maxx-minx)/TN;
s0[i].p.y=miny+i*(maxy-miny)/TN;
s0[i].e=max_r(s0[i].p);
}
for(double len=fmax(maxx-minx,maxy-miny);len>1e-7;len*=0.9)
for(u=0;u<=TN;u++)
{
double ang=(rand()%32768)*1.0/32768*6.283;
s[u].p.x=s0[u].p.x+sin(ang)*len;
s[u].p.y=s0[u].p.y+cos(ang)*len;
s[u].e=max_r(s[u].p);
if (s[u].e<s0[u].e) s0[u]=s[u];
}
int ans;
double anse;
anse=10000000;
for(i=0;i<=TN;i++)
if (anse>s0[i].e)
{
anse=s0[i].e;
ans=i;
}
printf("%.3lf %.3lf/n",s0[ans].p.x,s0[ans].p.y);
}
}