ABC314EX 题解

发布时间 2023-08-22 21:41:01作者: Aisaka_Taiga

模拟退火的题解。

题目的难点在于如何计算点到所有线段的距离。

我们可以通过求线段的直线解析式,然后计算与其垂直的直线的斜率,将随机出来的点带入求得直线解析式,然后求两直线交点,判断是否在线段上进行分讨即可,但是我可能写挂了,所以改成用的向量。

我们看到有三种情况,我们可以分别来讨论。

当为图 (a) 的情况时,我们可以用叉积来计算,我们都知道叉积的几何意义是以两个向量为边的平行四边形的面积,设向量 \(\overrightarrow{AB} (x1,y1),\overrightarrow{AP} (x2,y2))\) 则我们要求的 \(PC\) 为:

\[\frac{(\overrightarrow{AB}\cdot \overrightarrow{AP}) }{|\overrightarrow{AB} |}=\frac{x1\times y2 - x2 \times x1}{|\overrightarrow{AB} |} \]

当为图 (b) 的情况时,我们要的就是 \(|\overrightarrow{BP} |\)

当为图 (c) 的情况时,我们要的就是 \(|\overrightarrow{AP} |\)

然后我们利用点乘来判断上述三种情况。

若为 (b) 的情况,$\overrightarrow{BP} $ 在 $\overrightarrow{AB} $ 上的投影向量与 $\overrightarrow{AB} $ 同向;若为 (c) 的情况,$\overrightarrow{AP} $ 在 $\overrightarrow{AB} $ 上的投影向量与 $\overrightarrow{AB} $ 异向。

设 $\overrightarrow{AB}(x1,y1),\overrightarrow{AP}(x2,y2),\overrightarrow{BP}(x3,y3) $。

所以得出结论:

\[d = \left\{\begin{matrix} |\overrightarrow{AP} | & x1 \times x2 + y1 \times y2 < 0\\ |\overrightarrow{BP} | & x1 \times x3 + y1 \times y3 > 0\\ |\overrightarrow{AC} | & others \end{matrix}\right. \]

然后我们就有了一个快速的求以当前点为圆心符合要求的圆半径最小是多少了。

后面就是正常的退火流程了,然后就是痛苦的调参,建议目标温度调小一点,降温系数尽量接近 \(1\) 一点,这样能保证精度。

code:


#include <bits/stdc++.h>

#define int long long
#define DB double
#define N 10010

using namespace std;

int n;
DB ax, ay, ans;
 
struct sb
{
	DB x, y;
	inline DB len(){return sqrt(x * x + y * y);}
} a[N], b[N];
 
inline sb operator + (const sb &a, const sb &b){return (sb){a.x + b.x, a.y + b.y};}
inline sb operator - (const sb &a, const sb &b){return (sb){a.x - b.x, a.y - b.y};}
inline DB dot(sb a, sb b){return a.x * b.x + a.y * b.y;}
inline DB cross(sb a, sb b){return a.x * b.y - a.y * b.x;}
 
inline DB dis(sb p, sb a, sb b)
{
	sb x = p - a, y = p - b, z = b - a;//向量AP,BP,AB,终点坐标减起点坐标
	if(dot(x, z) < 0) return x.len();//AP在AB的投影向量与AB方向相反,AP向量的模
	else if (dot(y, z) > 0) return y.len();//BP在AB的投影向量与AB方向相同,BP向量的模
	else return fabs(cross(x, z)) / z.len();//利用叉积计算距离
}
 
inline DB calc(DB x, DB y)
{
	DB ans = -1e18;
	for (int i = 1; i <= n; ++i)
		ans = max(ans, dis((sb){x, y}, a[i], b[i]));
	return ans;
}
 
inline void SA()
{
    DB T = 1e3;
    while(T > 1e-13)
    {
		DB tx = ax + (DB)(2 * rand() - RAND_MAX) / RAND_MAX * T;
		DB ty = ay + (DB)(2 * rand() - RAND_MAX) / RAND_MAX * T;
		DB res = calc(tx, ty);
		if(res < ans) ans = res, ax = tx, ay = ty;
		else if(exp((ans - res) / T) * RAND_MAX > rand()) ax = tx, ay = ty;
        T *= 0.9996;
	}
    return ;
}
 
signed main()
{
	srand(time(0));
	cin >> n;
	for(int i = 1; i <= n; i ++)
    {
		cin >> a[i].x >> a[i].y >> b[i].x >> b[i].y;
		ax += (a[i].x + b[i].x) / 2;
		ay += (a[i].y + b[i].y) / 2;
	}
	ax /= n, ay /= n;
	ans = calc(ax, ay);
	while((DB)clock() / CLOCKS_PER_SEC < 1.5) SA();
	printf("%.10lf\n", ans);
	return 0;
}

AC 记录