题解:P4288 [SHOI2014] 信号增幅仪

news/2024/11/19 9:22:07/文章来源:https://www.cnblogs.com/NightTide/p/18434009

很好一题目,使我的最小圆覆盖旋转。

先假设 \(p = 1\)。这是最简单的情况。这个时候我们就得到了一个裸的最小圆覆盖。

\(p \not= 1\),但是 \(a = 0\) 的时候。圆就变成了对称轴与坐标轴平行的椭圆,运用高中知识仿射一下,又回到了最小圆覆盖。

在一般的情况下,我们先通过坐标的旋转回到第二种情况,再进行仿射,又是最小圆覆盖。

目前为止实际上这个题目已经做完了。如果你还不会做,那么你一定遇到了下面这个问题:

什么是最小圆覆盖

模板题传送门

最小圆覆盖问题是指:在二维平面上有一堆点,求一个最小半径的圆,能够将所有点全部都包住。

为解决这个问题,我们首先引入三个性质:

性质 1:最小覆盖圆是唯一的

证明:假设存在两个圆,它们的都是符合要求的最小圆。设它们的半径为 \(r\)。那么所有的点就必定落在下图中的蓝色区域:

而我们以两圆的两个交点为直径再做一个圆(如下图中紫色圆),显然它包含了该区域中的任意点,并且其直径为原本的两圆的一条弦,必然小于原本的两圆的直径:

那么,蓝色圆不为最小圆,与假设冲突。

性质 2:对于一个点集,其最小覆盖圆只为两种情况之一:1. 圆由该点集内三个或者三个以上的点共同确定;2. 该点集内的两个点确定圆的直径

证明:记满足该两种情况为命题 \(p\),是某个点集的最小覆盖圆为命题 \(q\)

假如点集内没有点落在圆上,那么显然圆保持圆心不变,一直收缩。必然能够到某一状态,使得至少一个点落在圆上。

假如点集内只有一个点落在圆上,保持该点始终在圆上收缩半径,也必定在某一状态,使得另一点落在圆上。

假如点集内有两点落在圆上,但两点不构成直径。让圆心在两点确定的线段的中垂线上移动,必然存在某一状态,使得除了这两点以外的某一点也落在圆上

综上,\(\neg p\Rightarrow \neg q\),即 \(p \Rightarrow q\)

对于一个只有两个点的点集,显然条件二为最小覆盖圆。

对于一个等边三角形的三个顶点构成的点集,显然条件一为最小覆盖圆。

综上,\(\exists\)\(O\) 满足 \(p\),使得 \(q\) 成立。

性质 3:对于一个已知点集 \(S\),已知其最小覆盖圆为圆 \(O_1\)。向点集内新增一点 \(P\),若 \(P\) 在圆外,则新点集的最小覆盖圆过点 \(P\)

证明:对于一个新圆 \(O_2\),其包含了点集 \(S\) 中的所有点和 \(P\),且 \(P\) 在圆 \(O_2\) 内。

\(O_2\) 上只有两个点。 \(S\) 中任意两点间的距离必然小于圆 \(O_1\) 的直径,那么这两点不可能构成 \(O_2\) 的直径,与性质 2 冲突。

\(O_2\) 上有三个或三个以上的点,如图(深蓝色为 \(O_1\),天青色为 \(O_2\)),\(S\) 中的点必然被包含在浅蓝色区域内:

img

类似于性质 1 的证明,圆 \(O_1\) 不为点集 \(S\) 的最小覆盖圆,与题设冲突。

于是得证。

引入这三个性质之后,我们就可以得到一个玄而又玄的算法:随机增量算法。

首先将所有的点随机化(随机化的作用在之后),记该点集为 \(S\)\(S_i\) 代表点集中的第 \(i\) 个点。

接着初始化一个半径为 \(0\),圆心为 \(S_1\) 的圆。

接着遍历所有点,将当前枚举到的点丢入已知点集中,并维护一个新的最小覆盖圆,以此类推。

问题在于,当我们已知前 \(i- 1\) 个点的最小覆盖圆时,如何确定加入第 \(i\) 个点后的最小覆盖圆。

\(S_i\) 在圆内,那么直接继续循环。

\(S_i\) 在圆外,由性质 3,新的最小覆盖圆过点 \(S_i\)。此时我们已知圆上一点,不足以确定一个圆。

于是我们又初始化一个半径为 \(0\),圆心为 \(S_i\) 的圆。从 \(i - 1\) 开始向前枚举 \(j\),此时性质 3 依然适用。

\(S_j\) 在圆内,那么直接继续循环。

\(S_j\) 在圆外,由性质 3,新的最小覆盖圆过点 \(S_j\)。此时我们已知圆上两点,依旧不够。

于是我们又初始化一个以 \(S_i\)\(S_j\) 为直径的圆,从 \(1 \sim j - 1\) 枚举 \(k\),依旧利用性质 \(3\)

\(S_k\) 在圆内,那么直接继续循环。

\(S_k\) 在圆外,由性质 3,新的最小覆盖圆过点 \(S_k\)。此时三点确定一个圆。

\(k\) 循环结束时,我们就得到了加入第 \(i\) 个点后的最小覆盖圆。

核心代码如下:

random_shuffle(p + 1, p + n + 1);// 随机化数组
// 结构体 (coor){x 坐标,y 坐标},数组 p 是该类型
// 结构体 (circle){圆心,半径}
c = (circle){p[1], 0};
for(int i = 2; i <= n; i++){// double_cmp(double a, double b):比较两个浮点数 a 和 b 的大小if(double_cmp(c.r, get_dis(c.o, p[i])) == -1){c = (circle){p[i], 0};for(int j = 1; j < i; j++){if(double_cmp(c.r, get_dis(c.o, p[j])) == -1){// get_dis(coor a, coor b):两点之间距离c = (circle){(p[i] + p[j]) / 2, get_dis(p[i], p[j]) / 2};for(int k = 1; k < j; k++){// get_circle(coor a, coor b, coor c):三点确定圆if(double_cmp(c.r, get_dis(c.o, p[k])) == -1) c = get_circle(p[i], p[j], p[k]);}}}}
}

好现在有人问,这玩意儿不是 \(O(n^3)\) 的吗?

这只是理论最坏时间复杂度。然而实际上,每次在进入下一层循环时,都会判定一次当前点是否在当前圆外。可以证明,当数据随机时,我们只有 \(\frac{3}{n}\) 的概率遇到一个在圆外的点。这也是随机化处理的原因。在随机化处理后,时间复杂度就变为了 \(O(n\times\frac{3}{n}\times n\times\frac{3}{n}\times n)\),即 \(O(n)\)

完整代码如下

#include<bits/stdc++.h>
#define MAXN 100010
#define eps 1e-9
#define pi acos(-1)
using namespace std;
struct coor{ double x, y; };
struct circle{ coor o; double r; };
coor operator + (coor a, coor b){ return (coor){a.x + b.x, a.y + b.y}; }
coor operator - (coor a, coor b){ return (coor){a.x - b.x, a.y - b.y}; }
coor operator * (coor a, double b){ return (coor){a.x * b, a.y * b}; }
coor operator / (coor a, double b){ return (coor){a.x / b, a.y / b}; }
double operator * (coor a, coor b){ return a.x * b.y - a.y * b.x; }
int double_cmp(double a, double b){if(fabs(a - b) < eps) return 0;if(a < b) return -1;return 1;
}
// 向量旋转 b,单位为弧度
coor rotate(coor a, double b){ return (coor){a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b)}; }
// 两点间距离
double get_dis(coor a, coor b){double dx = a.x - b.x;double dy = a.y - b.y;return sqrt(dx * dx + dy * dy);
}
// 两直线交点,p 和 v,q 和 w 分别确定两条直线
coor inter_section(coor p, coor v, coor q, coor w){coor u = p - q;double t = (w * u) / (v * w);return p + v * t;
}
// 求中垂线,用两点表示一条直线
pair<coor, coor> bisector(coor a, coor b){coor p = (a + b) / 2;coor q = rotate(b - a, pi/2);return make_pair(p, q);
}
// 三点定圆
circle get_circle(coor a, coor b, coor c){pair<coor, coor> x = bisector(a, b), y = bisector(a, c);coor o = inter_section(x.first, x.second, y.first, y.second);double r = get_dis(o, a);return (circle){o, r};
}
int n;
circle c;
coor p[MAXN];
int main(){scanf("%d", &n);for(int i = 1; i <= n; i++) scanf("%lf%lf",&p[i].x,&p[i].y);random_shuffle(p + 1, p + n + 1);c = (circle){p[1], 0};for(int i = 2; i <= n; i++){if(double_cmp(c.r, get_dis(c.o, p[i])) == -1){c = (circle){p[i], 0};for(int j = 1; j < i; j++){if(double_cmp(c.r, get_dis(c.o, p[j])) == -1){c = (circle){(p[i] + p[j]) / 2, get_dis(p[i], p[j]) / 2};for(int k = 1; k < j; k++){if(double_cmp(c.r, get_dis(c.o, p[k])) == -1) c = get_circle(p[i], p[j], p[k]);}}}}}printf("%.10lf\n",c.r);printf("%.10f %.10f\n",c.o.x, c.o.y);
}

回到本题

好的以上是最小圆覆盖,根据一开始的思路,我们很容易就能够将上面那份板子改为本题的答案。

#include<bits/stdc++.h>
#define MAXN 100010
#define eps 1e-9
#define pi acos(-1)
using namespace std;
struct coor{ double x, y; };
struct circle{ coor o; double r; };
coor operator + (coor a, coor b){ return (coor){a.x + b.x, a.y + b.y}; }
coor operator - (coor a, coor b){ return (coor){a.x - b.x, a.y - b.y}; }
coor operator * (coor a, double b){ return (coor){a.x * b, a.y * b}; }
coor operator / (coor a, double b){ return (coor){a.x / b, a.y / b}; }
double operator * (coor a, coor b){ return a.x * b.y - a.y * b.x; }
int double_cmp(double a, double b){if(fabs(a - b) < eps) return 0;if(a < b) return -1;return 1;
}
coor rotate(coor a, double b){ return (coor){a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b)}; }
double get_dis(coor a, coor b){double dx = a.x - b.x;double dy = a.y - b.y;return sqrt(dx * dx + dy * dy);
}
coor inter_section(coor p, coor v, coor q, coor w){coor u = p - q;double t = (w * u) / (v * w);return p + v * t;
}
pair<coor, coor> bisector(coor a, coor b){coor p = (a + b) / 2;coor q = rotate(b - a, pi/2);return make_pair(p, q);
}
circle get_circle(coor a, coor b, coor c){pair<coor, coor> x = bisector(a, b), y = bisector(a, c);coor o = inter_section(x.first, x.second, y.first, y.second);double r = get_dis(o, a);return (circle){o, r};
}
int n, a, t;
double b;
circle c;
coor p[MAXN];
int main(){scanf("%d", &n);for(int i = 1; i <= n; i++) scanf("%lf%lf",&p[i].x,&p[i].y);scanf("%d%d",&a,&t);b = a / 180.0 * pi;// 旋转for(int i = 1; i <= n; i++) p[i] = rotate(p[i], b);// 仿射for(int i = 1; i <= n; i++) p[i].x /= (double)t;random_shuffle(p + 1, p + n + 1);c = (circle){p[1], 0};for(int i = 2; i <= n; i++){if(double_cmp(c.r, get_dis(c.o, p[i])) == -1){c = (circle){p[i], 0};for(int j = 1; j < i; j++){if(double_cmp(c.r, get_dis(c.o, p[j])) == -1){c = (circle){(p[i] + p[j]) / 2, get_dis(p[i], p[j]) / 2};for(int k = 1; k < j; k++){if(double_cmp(c.r, get_dis(c.o, p[k])) == -1) c = get_circle(p[i], p[j], p[k]);}}}}}c.r *= 1000;c.r = round(c.r);c.r /= 1000;printf("%.3lf\n",c.r);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/804163.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python打包whl文件

在python中,使用setuptools库创建wheel包 确保已安装wheel和setuptools# 使用pip list 查看已经安装的包 pip list如果没有,就用下面的命令安装pip install wheel setuptools在当前路径创建一个aaa的文件夹 在aaa里创建一个__init__.py的文件,内容如下class test_class(obj…

idea启动 main 函数时,报 Command line is too long Shorten command xxx

idea启动 main 函数时,报 Command line is too long Shorten command xxx 一、解决办法找到 .idea 下面的 workspace.xml 找到以下属性 PropertiesComponent,添加 dynamic.classpath 属性<property name="dynamic.classpath" value="true" />结果…

信息学奥赛复赛复习04-CSP-J2019-04-加工零件-位运算、整数映射0或1、结构体、初始化列表构造、动态数组、二维动态数组、队列、宽度优先搜索

PDF文档公众号回复关键字:202409261 2019 CSP-J 题目4 加工零件 [题目描述] 凯凯的工厂正在有条不紊地生产一种神奇的零件,神奇的零件的生产过程自然也很神奇。工厂里有 n位工人,工人们从 1∼n 编号。某些工人之间存在双向的零件传送带。保证每两名工人之间最多只存在一条传送…

2024秋软件工程第一次结对作业

2024秋软件工程第一次结对作业之需求分析和原型设计 《构建之法》阅读成果 第3章 软件工程师的成长 这一章探讨了软件工程师在职业生涯中的成长路径,强调了不同阶段所需的技能和发展策略。以下是详细内容:初级工程师:技能要求:掌握基本的编程语言、开发工具和版本控制系统。…

人工智能下的GIS发展趋势

地理信息系统(GIS)与人工智能(AI)的结合正在开启智能地理信息时代的新篇章。随着AI技术的不断进步,GIS的应用前景变得更加广泛和深入,不仅在提高工作效率、提升分析精度方面展现出巨大潜力,还在促进资源共享、推动跨行业和跨领域协同发展方面发挥着重要作用。 数据采集与…

Flink-Yarn模式修改Task Slot的数量

1.修改 Flink 配置文件 (flink-conf.yaml) Flink 中的 TaskManager 是根据 slots 来分配任务的,默认情况下,一个 TaskManager 可以有多个 slots。你可以通过调整 flink-conf.yaml 中的以下配置来控制每个 TaskManager 的 slot 数量: taskmanager.numberOfTaskSlots: <num…

Linux服务器运维管理面板1Panel快速安装及安全配置

1Panel 是一个现代化、开源的 Linux 服务器运维管理面板,旨在帮助运维人员简化服务器管理任务。它提供了直观的界面和强大的功能,使用户可以通过图形化操作界面对服务器进行管理,减少了对命令行的依赖。1Panel 支持多种操作系统,适用于 Linux 服务器,提供了如网站管理、数…

怎么查看网站是否被谷歌收录,怎么查看网站是否被谷歌收录的办法

要查看网站是否被谷歌收录,可以采用以下几种办法: 一、使用谷歌搜索引擎的“site:”指令 这是最直接且常用的方法之一。具体步骤如下: 打开谷歌搜索引擎:在浏览器中打开Google.com,确保使用的是谷歌的官方搜索引擎。 输入查询指令:在搜索框中输入“site:”加上你的网站域…

【YashanDB知识库】YMP迁移oracle不兼容给用户授权高级包

本文转自YashanDB官网,具体内容请见https://www.yashandb.com/newsinfo/7441382.html?templateId=1718516 【标题】YMP迁移oracle不兼容给用户授权高级包 【关键字】oracle迁移,高级包授权 【问题描述】迁移评估任务中,oracle迁移YashanDB,YMP不兼容语句:grant execute o…

FICO:常规配置

FICO后台常规配置 定义mySAP系统中的国家: Tcode:OY01 SAP系统中的国家已经提前定义好了,无需自行配置,此处只做查看演示 检查货币代码 Tcode:OY03 SAP系统中的常规的货币代码也已经提前定义好了,一般不做更改此处只做查看演示为货币设置小数位数: Tcode:OY04 SAP系统中货币的小…

183天打造行业新标杆!BOE(京东方)国内首条第8.6代AMOLED生产线提前全面封顶

2024年9月25日,BOE(京东方)投建的国内首条第8.6代AMOLED生产线全面封顶仪式在成都市高新区举行,该生产线从开工到封顶仅用183天,以科学、高效、高质的速度再树行业新标杆。这不仅是BOE(京东方)创新突破、打造新质生产力的又一重大举措,也是OLED领域的里程碑事件,极大推…

按内容关键字批量查找文件并导出的方法

按内容关键字批量查找文件并导出的方法 文件批量查找复制导出 软件下载地址:http://6laohu.com 将指定目录下所有文件 按文件名中的关键字或文件内容中出现的关键字查找你需要的那些文件 并全部整理复制到指定文件夹下