题解:P4586 [FJOI2015] 最小覆盖双圆问题

news/2025/2/27 19:57:04/文章来源:https://www.cnblogs.com/easy42/p/18741818

写了这么久终于过了,发篇题解记录一下。

第一次写黑题题解,写的不好请见谅。

目录

  1. 本题思路
  2. 三点定圆
  3. 最小圆覆盖
  4. 关于最小圆覆盖时间复杂度
  5. 回到本题
  6. 二分法划分点集
  7. 总时间复杂度
  8. 最小覆盖双圆问题代码

本题思路

首先,这道题叫做最小覆盖双圆问题,这道题涉及到一个叫做最小圆覆盖的问题。

一个容易想到的思路:若我们能做出最小圆覆盖的问题,就可以把本题分成两个圆,也就是两个点集来处理。

那我们如何处理最小圆覆盖的问题呢?

三点定圆

首先,不共线的三个点可以确定一个圆。

作任意两点所在线段的中垂线,三条中垂线的交点就是这三个点共圆的圆心。这个圆心到三点中的任意一点就是这个圆的半径。当三点不在同一条直线时。形成一个三角形,而三角形有且只有一个外接圆。

数学原理是中垂线上的点到线段两端的距离相等。两条中垂线的交点,到两条线段的距离都相等。所以,不在同一条直线的三点可以确定一个圆。

附三角形的外切圆半径公式:\(\frac{abc}{4S}\),其中,\(S\) 表示三角形的面积,\(a\)\(b\)\(c\) 分别为三角形的三条边。

本部分代码:

Point circle_center(const Point a,const Point b,const Point c){Point center;double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;double d=a1*b2-a2*b1;center.x=a.x+(c1*b2-c2*b1)/d;center.y=a.y+(a1*c2-a2*c1)/d;return center;
}

最小圆覆盖

根据以上结论,我们知道对于任意三个不共线的点,我们可以求出三点定圆,所以一个明显的想法就是枚举三个点。

具体步骤如下:

  1. 枚举第一个点 \(i\),若不在目前圆内,设它为圆心。

  2. 枚举第二个点 \(j\),若不在当前圆内,设当前圆为以 \(i\)\(j\) 为直径的圆。

  3. 枚举第三个点 \(k\),若不在当前圆内,设当前圆为 \(i\)\(j\)\(k\) 的外接圆。

显然最优解一定是两个点为直径的圆或者一个三角形的外接圆,否则肯定能缩的更小。这样做就是正确的。

这就是所谓的增量法。

关于最小圆覆盖时间复杂度

显然,按上面的做法,时间复杂度是 \(O(n^3)\) 的,会被不良心的出题人卡掉。

所以,我们需要打乱数据,从而提升时间复杂度。

这就是所谓的随机增量法。

时间复杂度期望 \(O(n)\)

最小圆覆盖代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define eps 1e-8
const int N=1e5+1;
int sgn(double x){if(fabs(x)<eps) return 0;else return x<0?-1:1;
}
struct Point{double x,y;};
double Distance(Point A,Point B){return hypot(A.x-B.x,A.y-B.y);};
Point circle_center(const Point a,const Point b,const Point c){Point center;double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;double d=a1*b2-a2*b1;center.x=a.x+(c1*b2-c2*b1)/d;center.y=a.y+(a1*c2-a2*c1)/d;return center;
}
void min_cover_circle(Point *p,int n,Point &c,double &r){random_shuffle(p,p+n);c=p[0],r=0;for(int i=1;i<n;i++){if(sgn(Distance(p[i],c)-r)>0){c=p[i];r=0;for(int j=0;j<i;j++){if(sgn(Distance(p[j],c)-r)>0){c.x=(p[i].x+p[j].x)/2;c.y=(p[i].y+p[j].y)/2;r=Distance(p[j],c);for(int k=0;k<j;k++){if(sgn(Distance(p[k],c)-r)>0){c=circle_center(p[i],p[j],p[k]);r=Distance(p[i],c);}}}}}}
}
Point p[N];
signed main(){int n;cin>>n;for(int i=0;i<n;i++) cin>>p[i].x>>p[i].y;Point c;double r;min_cover_circle(p,n,c,r);printf("%.10f\n%.10f %.10f\n",r,c.x,c.y);return 0;
}

最小圆覆盖例题:这里。

回到本题

之前说若我们能做出最小圆覆盖的问题,就可以把本题分成两个圆,也就是两个点集来处理。

现在我们已经会了最小圆覆盖的问题,如何划分点集呢?

如果暴力枚举,显然不行。

这时引出我们的二分法。

二分法划分点集

用二分求出一条与 \(X\) 轴垂直的直线,然后再求解。

如果正解是斜着画一条直线呢?

这时,我们旋转坐标系

每次旋转之后都求一次最小双圆覆盖,然后再取最优值即是答案。如何对每个点进行“旋转”?

可以看图来理解。

坐标系旋转 \(180\) 度就是原坐标系了,所以只要转 \(180\) 度就好了。

总时间复杂度

首先,要转 \(180\) 次,每次中要二分直线,再做最小圆覆盖。

总时间复杂度 \(O(n \log n)\)

最小覆盖双圆问题代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define eps 1e-8
int n;
const int N=1e5+1;
struct Point{double x,y;};
Point p[N],a[N];
double Distance(Point A,Point B){return hypot(A.x-B.x,A.y-B.y);};
int cmp(Point a, Point b){return a.x<b.x;}
int sgn(double x){if(fabs(x)<eps) return 0;else return x<0?-1:1;
}
Point rotate(const Point &b) {Point t;t.x=b.x*cos(1.0/180*acos(-1))+b.y*sin(1.0/180*acos(-1));t.y=b.y*cos(1.0/180*acos(-1))-b.x*sin(1.0/180*acos(-1));return t;
}
Point circle_center(const Point a,const Point b,const Point c){Point center;double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;double d=a1*b2-a2*b1;center.x=a.x+(c1*b2-c2*b1)/d;center.y=a.y+(a1*c2-a2*c1)/d;return center;
}
double min_cover_circle(int n1,int n2){if(n1>n2) return 0;for(int i=n1;i<=n2;i++) a[i]=p[i];random_shuffle(a+n1+1,a+n2+1);Point c=a[n1];double r=0;for(int i=n1;i<=n2;i++){if(sgn(Distance(a[i],c)-r)>0){c=a[i];r=0;for(int j=n1;j<i;j++){if(sgn(Distance(a[j],c)-r)>0){c.x=(a[i].x+a[j].x)/2;c.y=(a[i].y+a[j].y)/2;r=Distance(a[j],c);for(int k=n1;k<=j;k++){if(sgn(Distance(a[k],c)-r)>0){c=circle_center(a[i],a[j],a[k]);r=Distance(a[i],c);}}}}}}return r;
}
void init(int n){double R=1e9;for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;for(int i=1;i<=180;i++){  for(int i=1;i<=n;i++) p[i]=rotate(p[i]);sort(p+1,p+n+1,cmp);int l=1,r=n;while(l<=r) {int mid=(l+r)/2;double R1=min_cover_circle(1,mid),R2=min_cover_circle(mid+1,n);if(min(R1,R2)>R) break; if(R1<R2)l=mid+1;else r=mid-1;R=min(R,max(R1, R2));}}printf("%.2f\n",R);
}
signed main(){while(cin>>n&&n!=0){init(n);}return 0;
}

如果本篇题解还可以,就点个赞吧!

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

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

相关文章

sed undefined label on MacOS, FreeBSD

A quick fix is to prepend your string expression with an empty string: For example: instead of sed -i s/foo/bar/g text.txt write: sed -i s/foo/bar/g text.txtThis should work across different OS (Linux, MacOS, Windows) --dopexxx来源:http://stackoverflow.…

软工作业1:自我介绍+软工5问

这个作业属于哪个课程 软工23级这个作业要求在哪里 自我介绍+软工五问这个作业的目标 熟悉博客园以及Github的相关操作,了解软件工程相关内容1.自我介绍兴趣爱好:羽乒人,爱听歌,电影,楷书,行楷 编程语言:C,JAVA 目标:后端开发2.软工五问软件工程在对就业上的帮助的具体…

对“推箱子”小游戏代码的改进

一.代码来源 https://www.cnblogs.com/heyu123/p/14844284.html 二.运行环境 DEV--C++ 三.原代码及其运行结果 原代码: int map[8][8]={{1,1,1,1,1,1,1,1},//0 空地 {1,0,0,0,1,0,0,1},//1 墙 {1,0,1,0,1,4,3,1},//3 目的地 {1,0,0,0,0,4,3,1},//4 箱子 {1,0,1,0,1,4,3,1},//5…

软件开发与创新课程设计作业——软件逆向设计

一、来源:软件工程2班李鹏飞去年的大作业`点击查看代码 #include <iostream> #include <string> #include <fstream> using namespace std;//定义客户类型 enum eGuestType // 在高版本VS中,需要用enum class,在低版本的vs中,直接用enum也可以 {e_member…

LVI_SAM 虚拟机安装复现(一)

0. 前言 高能警告:LVI_SAM 的安装步骤是繁琐的,一个坑接着一个坑,请预留48+小时的安装时间,和80%以上的san值。非战斗人员请尽快撤离。 预备知识:虚拟机安装步骤,ROS基本概念,Makefile工作原理 没有预备知识的话,也没关系,本文也不会给你解释的( 本文是第一大步骤,即…

大模型--三种三种检索方式-Dense retrieval / Lexical Retrieval / Multi-Vector Retrieval- 44

1. 参考 M3-Embedding https://github.com/FlagOpen/FlagEmbedding https://arxiv.org/pdf/2402.03216 https://huggingface.co/BAAI/bge-m3 2. Dense retrievalimport torch import torch.nn as nnclass DenseRetrieval(nn.Module):def __init__(self, embedding_dim):super(D…

从拉新到留存,用户生命周期分析全流程

已收藏分享从拉新到留存,用户生命周期分析全流程 2025-02-17 17:02人人都是产品经理在当今竞争激烈的市场环境中,理解并管理用户生命周期是实现用户增长和留存的关键。本文将深入剖析用户生命周期的全流程管理,从拉新到留存,详细解读不同业务类型(如消费品、耐用品、平台型…

朴素贝叶斯其实并不朴素

朴素贝叶斯英文名称NaiveBayes,朴素贝叶斯确实nave,但是并不朴素,而是简单,并不是逻辑上面的简单,而是假设上面的简单。 1.贝叶斯公式 ​ 其中: P(C|X)是类C在给定特征X下的后验概率。 P(X|C)是特征X在给定类C下的条件概率,也叫做似然。 P(C)是类C的先验概率。 P(X)是特…

NocoBase 本周更新汇总:新增路由管理

本周更新包括:支持为页面标签页配置权限,新增路由管理页面等。汇总一周产品更新日志,最新发布可以前往我们的博客查看。 NocoBase 目前更新包括的版本更新包括三个分支:main ,next和 develop。main :截止目前最稳定的版本,推荐安装此版本。 next:包含即将发布的新功能,…

AI安全-模型用户输入注入

顾名思义,在调用AI大模型时,根据用户传入的数据,进行AI处理,调用插件,但模型后端需要调用API,API需要传入的username一个小场景,企业微信对话调用AI去修改当前密码 假设开发者设计如下: 用户输入-> AI -> 调用插件修改密码 修改密码插件实现:a.com/change_passw…

基于Microsoft.Extensions.VectorData实现语义搜索

本文介绍了Microsoft.Extensions.Vector的基本概念 和 基本使用,结合Embedding Model(如all-minilm) 和 VectorStore(如Qdrant),我们可以快速实现语义搜索,而不仅仅是关键字匹配。如果你也是.NET程序员希望参与AI应用的开发,那就快快了解和使用基于Microsoft.Extensioi…