【2019-WHU网络赛-A.circle】模拟退火

题意

在坐标轴上给出n条线段,圆心在x轴的[0,L]上移动,在该圆不和任何线段相交的情况下最大的半径是多少?

题解

模拟退火,因为模拟退火可以并行执行,所以先随机出多个点的位置(多组可能解),然后对这些解进行模拟退火,得出点到线段的最短距离用的是失量法。

点与线段一共会有下列两种位置。

  1. P在AB上,那么P离AB的最短距离便是 A × B / l e n A B {A×B}/{len_{AB}} A×B/lenAB,这里的乘是向量叉乘(△ABP的面积)。
  2. P不在AB上,那么AB·AP 和 AB·BP 肯定是一个锐角(>0)一个钝角(<0),所以这时候只要取 max(PA,PB)即可。这里的·是向量点乘。

出处:刘汝佳训练指南几何专题
【2019-WHU网络赛-A.circle】模拟退火_第1张图片
【2019-WHU网络赛-A.circle】模拟退火_第2张图片
【2019-WHU网络赛-A.circle】模拟退火_第3张图片

代码

#include 
using namespace std;

const int maxn = 1000 + 5;
const int rndcnt = 10;
const double eps = 1e-5;
struct Point{
	double x, y;
};

struct Line{
	Point s, e;
};

Line ll[maxn];
double p[rndcnt];

double dot(Point x, Point y) {
	return x.x * y.x + x.y * y.y;
}

double det(Point a, Point b) {
	return a.x * b.y - a.y * b.x;
}

double dist(Point a, Point b) {
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

Point sub(Point a, Point b) {
	return Point{a.x - b.x, a.y - b.y};
}

// 这是WHU大佬的点到线段最短距离的模板我也看不懂
double point2seg(double x, const Line &l) {
	Point P{x, 0};
	Point v = sub(l.e, l.s);
	Point w = sub(P, l.s);
	double c1, c2;
	if ((c1 = dot(w, v)) < eps) {
		return dist(P, l.s);
	}
	if ((c2 = dot(v, v)) < c1 + eps) {
		return dist(P, l.e);
	}
	double b = c1 / c2;
	return dist(P, Point{l.s.x + b * v.x, l.s.y + b * v.y});
}

void sol() {
	int n, L;
	double ans = -1e100;
	double step = 100;
	scanf("%d%d", &n, &L);
	step = L;
	for (int i = 0; i < n; i++) {
		scanf("%lf%lf%lf%lf", &ll[i].s.x, &ll[i].s.y, &ll[i].e.x, &ll[i].e.y);
	}
	for (int i = 0; i < rndcnt; i++) {
		p[i] = 1. * rand() / RAND_MAX * L; // 随机10组解
	}
	double rans = 0;
	while (step > eps) {
		for (int i = 0; i < rndcnt; i++) {
			double tmin = -1e100;
			double rmin = p[i];
			for (int j = 0; j < rndcnt; j++) {
				double cmin = 1e100;
				double nxt = p[i] + step * (1. * rand() / RAND_MAX - 0.5);
				if (nxt < 0 || nxt > L) continue;
				for (int k = 0; k < n; k++) {
					double tans = point2seg(nxt, ll[k]);
					cmin = min(cmin, tans);
				}
				if (cmin > tmin) {
					tmin = cmin;
					rmin = nxt;
				}
			}
			p[i] = rmin;
			if (tmin > ans) {
				rans = rmin;
				ans = tmin;
			}
		}
		step = step * 0.95;
	}
	cerr << rans << endl;
	printf("%.3f\n", ans);
}

int main() {
	int t;
	srand(time(NULL));
	scanf("%d", &t);
	while (t--) sol();
	return 0;
}

你可能感兴趣的:(计算几何,模拟退火)