模拟退火算法的理论讲解:
求一个半径最小的球体,包含所有的点。模拟退火,不断缩小半径,搜索……
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 33
#define eps 1e-7
struct POINT {
double x, y, z;
} p[N], s;
int n;
inline double dist(const POINT &a, const 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));
}
void solve() {
s.x = s.y = s.z = 0;
double ans = 1e20, delta = 100;
while (delta > eps) {
int d = 0;
for (int i=1; i<n; i++)
if (dist(s, p[i]) > dist(s, p[d]))
d = i;
double md = dist(s, p[d]);
if (ans > md) ans = md;
s.x += (p[d].x-s.x)/md*delta;
s.y += (p[d].y-s.y)/md*delta;
s.z += (p[d].z-s.z)/md*delta;
delta *= 0.98;
}
printf("%.5lf\n", ans);
}
int main() {
while (scanf("%d", &n) == 1 && n) {
for (int i=0; i<n; i++)
scanf("%lf%lf%lf", &p[i].x, &p[i].y, &p[i].z);
solve();
}
return 0;
}
基础模拟退火题目。
在平面内部随机取NUM个点,然后对每一个点进行“退火”(随机向各个方向移动T次,寻求最优结果,不断增大圆的半径)。
从NUM个结果中找到半径最大的点即可。
附上一篇国家集训队论文:浅谈随机化思想在几何问题中的应用
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <cstdlib>
using namespace std;
#define N 1005
#define NUM 30
#define eps 1e-2
struct POINT {
double x, y, d;
POINT() {}
POINT(double _x, double _y): x(_x), y(_y) {}
} s, p[N], rp[NUM];
int x, y, n;
double dis(POINT a) {
double ret = 1e20;
double tmp;
for (int i=0; i<n; i++) {
tmp = sqrt((a.x-p[i].x)*(a.x-p[i].x) + (a.y-p[i].y)*(a.y-p[i].y));
if (tmp < ret) ret = tmp;
}
return ret;
}
void Init() {
scanf("%d%d%d", &x, &y, &n);
for (int i=0; i<n; i++)
scanf("%lf %lf", &p[i].x, &p[i].y);
double d;
for (int i=0; i<NUM; i++) {
rp[i].x = rand()%x + 1;
rp[i].y = rand()%y + 1;
rp[i].d = dis(rp[i]);
}
}
void solve() {
double delta = (double)max(x, y)/sqrt((double)n)+1;
double theta;
POINT t;
while (delta > eps) {
for (int i=0; i<NUM; i++) {
for (int k=0; k<NUM; k++) {
theta = rand();
t.x = rp[i].x + cos(theta)*delta;
t.y = rp[i].y + sin(theta)*delta;
t.d = dis(t);
if (0<=t.x && t.x<=x && 0<=t.y && t.y<=y) {
if (t.d > rp[i].d) rp[i] = t;
}
}
}
delta *= 0.8;
}
int k = 0;
for (int i=1; i<NUM; i++)
if (rp[k].d < rp[i].d) k = i;
printf("The safest point is (%.1lf, %.1lf).\n", rp[k].x, rp[k].y);
}
int main() {
int cas;
scanf("%d", &cas);
while (cas--) {
Init();
solve();
}
return 0;
}
求费马点:到所有点距离和最短的点称之为费马点。然后输出最短距离和。简单的模拟退火。
上代码:里面向不同方向走的时候,才用了随机化的方法,我取了10这个常数,这个理论上没有要求必须上下左右四个方向或者八个方向什么的,满足随机即可,这个常数的选择应该是比较凑巧可以过掉题目的。Just so so 了哈。。。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 110
#define eps 1e-2
struct POINT {
double x, y;
} p[N], s, c;
int n;
double dist(POINT &a) {
double ret = 0;
for (int i=0; i<n; i++)
ret += sqrt((p[i].x-a.x)*(p[i].x-a.x)+(p[i].y-a.y)*(p[i].y-a.y));
return ret;
}
void solve() {
s.x = s.y = 0;
for (double delta = 10000.0; delta>eps; delta*=0.9) {
for (int i=0; i<10; i++) {
double t = rand();
c.x = s.x + cos(t)*delta;
c.y = s.y + sin(t)*delta;
if (dist(c) < dist(s)) s = c;
}
}
printf("%.0lf\n", dist(s));
}
int main() {
scanf("%d", &n);
for (int i=0; i<n; i++)
scanf("%lf%lf", &p[i].x, &p[i].y);
solve();
return 0;
}