洛谷 P1742 最小圆覆盖

本文深入探讨了如何找出包含所有给定点的最小圆,通过数学归纳法和三点共圆原理,详细介绍了算法步骤及其实现代码,适用于解决几何优化问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

给出N个点,让你画一个最小的包含所有点的圆。

分析

这个问题实际上是找出三个点确定的一个圆,能包含其他所有点。

与三点共圆有密切的关系,先来说三点共圆。
三点共圆有两种情况:三个点共线或不共线,共线则两个最远点构成直径,不共线则圆为三角形外接圆
重点考虑外接圆情形,班经用正弦定理是比较好找的,其实难度是找圆心,也就是用三角形三个顶点坐标来表示外心坐标。

这里代码中的三点共圆圆心计算参考了这篇文章
其实也有其他的办法如两条线的中垂线交圆心,向量垂直过中点交圆心等,不过代码上用三角形三点坐标直接实现比较容易

下面来谈最小圆覆盖,其实用的是数学归纳法,如果已经覆盖了前 i 个点,就想办法造一个圆让其覆盖 i+1 个点。

下面考虑步骤:
1.已知 1 ~ i-1 个点已经由圆 C i − 1 C_{i-1} Ci1覆盖,现在欲加入第 i 个点,让它也被圆覆盖
2.如果第 i 个点已经在圆 C i − 1 C_{i-1} Ci1中,则下一个圆 C i = C i − 1 C_i = C_{i-1} Ci=Ci1,并且回到第一步继续
3.如果不在,则需要构造新的圆包含 1 ~ i 个点,首先这个新加入的点 i 需要在圆边上(临界情况),这也就确定了圆上的一个点
4.用第一个点和第 i 个点造圆 T 1 T_{1} T1,这时如果有一个点 j (j<i) 不在 T 1 T_{1} T1内,就把它取作 C i C_i Ci边上的第二个点。
5.用 i 和 j 两个点造圆 T 2 T_2 T2,同4步,找不在圆 T 2 T_2 T2内的点 k (k<j<i),如果存在,那么k就是圆 C i C_i Ci边上的第三个点。
6.现在 i j k 三个点都在圆 C i C_i Ci 上,且能证明 C i C_i Ci 包含了 1~i 所有点,于是用三点共圆构造 C i C_i Ci,回到1继续迭代。
迭代到 C n C_n Cn即结束

这看似是三层循环,其实期望复杂度 O(n) ,但为了防止数据不合适,在处理前要先打乱给的 n 个点,用 random_shuffle() 可以实现

代码

#include <iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<math.h>
#include<iomanip>
using namespace std;
struct Point
{
	double x, y;
	Point() {}
	Point(double x,double y):x(x),y(y){}
};//存点结构体
inline double dis(Point &p1, Point &p2)//两个点之间的距离
{
	return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));

}
inline double sqr(double x) { return x * x; }//平方
class min_circle {
private:
	Point p[100005];
	int n;
public:
	Point center;//代表每一次构造出圆的圆心
	double r;//代表每一次构造的半径
	void init(int n)
	{
		this->n = n;
		for (int i = 0; i < n; i++)cin >> p[i].x >> p[i].y;
		random_shuffle(p, p + n);//打乱,避免卡时
	}

	void circular()//构造圆序列
	{
		r = 0, center = p[0];
		for (int i = 1; i < n; i++)//尝试判定第i个点在不在圆内,如果在则进入下一个点,不在则造一个p[i]在边上的圆
			if (dis(center, p[i]) - r > 1e-8)//p[i]在之前造的圆外
			{
				r=dis(p[0],p[i])/2;//以p[0]和p[i]为直径造一个圆,找出在这个圆外的点p[j],作为第二个在本次构造圆边上的点
				center.x = (p[0].x + p[i].x) / 2;
				center.y= (p[0].y + p[i].y) / 2;
				for (int j = 0; j < i; j++)
				{
					if (dis(center, p[j]) - r > 1e-8)//发现了这种圆外的j
					{
						r = dis(p[i], p[j]) / 2;//p[i]和p[j]是两个本轮最终构造圆边上的点,就以这两者为直径构造一个圆,尝试找在其外部的k点
						center.x = (p[i].x + p[j].x) / 2;
						center.y = (p[i].y + p[j].y) / 2;
						for (int k = 0; k < j; k++)
						{
							if (dis(center, p[k]) - r > 1e-8)//发现这样的k
							{
								center = build_circle(p[i], p[j], p[k]);//发现p[i],p[j],p[k]都在本轮构造圆边上,圆被确定了
								r = dis(center, p[i]);
							}
						}
					}
				}
			}
	}

	Point build_circle(Point a, Point b, Point c) {//三点造圆
		double a1, a2, b1, b2, c1, c2;
		Point ans;
		a1 = 2 * (b.x - a.x), b1 = 2 * (b.y - a.y),
			c1 = sqr(b.x) - sqr(a.x) + sqr(b.y) - sqr(a.y);
		a2 = 2 * (c.x - a.x), b2 = 2 * (c.y - a.y),
			c2 = sqr(c.x) - sqr(a.x) + sqr(c.y) - sqr(a.y);
		if (fabs(a1-0.0)<1e-8) {
			ans.y = c1 / b1;
			ans.x = (c2 - ans.y * b2) / a2;
		}
		else if (fabs(b1-0)<1e-8) {//因为分母上有这两者,这两种情况特判
			ans.x = c1 / a1;
			ans.y = (c2 - ans.x * a2) / b2;
		}
		else {
			ans.x = (c2 * b1 - c1 * b2) / (a2 * b1 - a1 * b2);
			ans.y = (c2 * a1 - c1 * a2) / (b2 * a1 - b1 * a2);
		}
		return ans;
	}
}MC;
int main()
{
	ios::sync_with_stdio(false);
	int n;
	cin >> n;
	MC.init(n);//读入 与 随机化,打乱
	MC.circular();
	cout << fixed << setprecision(10) << MC.r << endl << MC.center.x << " " << MC.center.y;
	return 0;
}
内容概要:文章详细介绍了电梯门禁(梯控)系统的硬件安装与接线要点。首先强调了梯控板与楼层按键对接的重要性,包括遵循一一对应原则以避免错层、越层问题,允许空层存在以适应实际需求。接着阐述了不同接线方式(COM、NO、NC端口的不同组合)对用户权限的影响,如单层权限用户刷卡直达指定楼层,多层权限用户在特定接线方式下的操作限制。硬件安装方面,强调了无源干触点设计原则以确保电气隔离,防止系统间干扰,以及读卡器接入时的规范要求。文章还介绍了梯控系统的技术原理,如身份验证机制(二维码/IC卡/人脸识别)、消防联动功能(紧急情况下释放所有楼层权限),并指出该系统适用于小区、写字楼等场景,支持机器人乘梯SDK扩展。最后,根据不同场景需求提出了适用的接线方式选择,如严格管控场景下选择4.3接线以实现精准权限控制,限制多层用户手动选层场景下选择4.1接线并配合软件权限设置。; 适合人群:从事电梯安装维护的技术人员、楼宇自动化工程师及相关领域的管理人员。; 使用场景及目标:①指导技术人员正确安装和接线梯控系统,确保系统安全稳定运行;②帮助管理人员了解不同接线方式对用户权限的影响,以便根据实际需求选择合适的配置方案;③提升楼宇安全管理和服务质量,特别是在小区、写字楼等场所的应用。; 其他说明:梯控系统的正确安装和接线不仅关系到系统的正常运作,更直接影响到用户的安全和使用体验。因此,在实际操作中务必严格按照规范执行,同时关注最新的技术发展和应用场景变化,以确保系统始终处于最佳状态。
03-21
### 关于动态规划 (Dynamic Programming, DP) 的解决方案 在解决洛谷平台上的编程问题时,尤其是涉及动态规划的题目,可以采用以下方法来构建解决方案: #### 动态规划的核心思想 动态规划是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。其核心在于存储重复计算的结果以减少冗余运算。通常情况下,动态规划适用于具有重叠子问题和最优子结构性质的问题。 对于动态规划问题,常见的思路包括定义状态、转移方程以及边界条件的设计[^1]。 --- #### 题目分析与实现案例 ##### **P1421 小玉买文具** 此题是一个典型的简单模拟问题,可以通过循环结构轻松完成。以下是该问题的一个可能实现方式: ```cpp #include <iostream> using namespace std; int main() { int n; cin >> n; // 输入购买数量n double p, m, c; cin >> p >> m >> c; // 输入单价p,总金额m,优惠券c // 计算总价并判断是否满足条件 if ((double)n * p <= m && (double)(n - 1) * p >= c) { cout << "Yes"; } else { cout << "No"; } return 0; } ``` 上述代码实现了基本逻辑:先读取输入数据,再根据给定约束条件进行验证,并输出最终结果[^2]。 --- ##### **UOJ104 序列分割** 这是一道经典的区间动态规划问题。我们需要设计一个二维数组 `f[i][j]` 表示前 i 次操作后得到的最大价值,其中 j 是最后一次切割的位置。具体实现如下所示: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 5e3 + 5; long long f[MAXN], sumv[MAXN]; int a[MAXN]; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n,k; cin>>n>>k; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++)sumv[i]=sumv[i-1]+a[i]; memset(f,-0x3f,sizeof(f)); f[0]=0; for(int t=1;t<=k;t++){ vector<long long> g(n+1,LLONG_MIN); for(int l=t;l<=n;l++)g[l]=max(g[l-1],f[t-1][l-1]); for(int r=t;r<=n;r++)f[r]=max(f[r],g[r]+sumv[r]*t); } cout<<f[n]<<'\n'; return 0; } ``` 这段程序利用了滚动数组优化空间复杂度,同时保持时间效率不变[^3]。 --- ##### **其他常见问题** 针对更复杂的路径覆盖类问题(如 PXXXX),我们往往需要结合一维或多维动态规划模型加以处理。例如,在某些场景下,我们可以设定 dp 数组记录到达某一点所需最小代价或者最大收益等指标[^4]。 --- ### 总结 以上展示了如何运用动态规划技巧去应对不同类型的算法挑战。无论是基础还是高级应用场合,合理选取合适的数据结构配合清晰的状态转换关系都是成功解决问题的关键所在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值