洛谷P1783 海滩防御

本文探讨了一种用于游戏地图中封锁路径的算法,通过计算信号塔的最小工作半径,确保敌人无法穿越海滩到达内陆。利用并查集和二分搜索技巧,文章详细介绍了如何判断信号塔覆盖范围,实现地图的完全封锁。

链接:P1783

题目描述
WLP同学最近迷上了一款网络联机对战游戏(终于知道为毛JOHNKRAM每天刷洛谷效率那么低了),但是他却为了这个游戏很苦恼,因为他在海边的造船厂和仓库总是被敌方派人偷袭。于是,WLP动用了他那丰满且充实的大脑(或许更偏向前者),想出了一个好主意,他把海滩分成垂直于海岸线的若干列,在其中的几列上放置几个信号塔,试图来监视整个海滩。然而,WLP是一个非常心急的人,他把信号塔建好后才发现还需给信号塔供能,它们才能投入使用(这不是废话么),它们都有一个工作半径,一个圆形区域里的所有敌人都逃不过它们的监视,不过,WLP发现,敌人们非常狡猾,除非他将道路完全封死,否则WLP的敌人可以走过一条任意弯曲的路(不一定走整点,但是不会出第0列和第N列构成的边界)来偷他的东西。

于是,WLP就思考了:到底需要给每个信号塔多大的工作半径,才能将从海滩到内地的路径完全封死呢?他再次动用了他那丰满且充实的大脑,想了一堂数学课,终于,还是没想出来。于是,他向LZZ神犇求助(额……C_SUNSHINE的身份是不是暴露了)。

终于,在WLP:“%!*@#!*(*!*#@$^&(此处省略无数卖萌场景)”的哀求下,LZZ神犇写了一个程序,在1s内就解决了问题。但是,邪恶的LZZ神犇决定要将这个难题共享给无数无辜的OIer,所以,现在轮到你了。

输入格式
第一行两个整数N和M:表示海滩被WLP分成的列数0-N和信号塔个数。

第2-M+1行:每行两个数Xi,Yi表示1-M号信号塔所在的列数和离开海滩的距离。

输出格式
一行一个实数,表示最小的工作半径,保留两位小数。

输入输出样例
输入 #1
5 5
1 5
3 5
5 5
4 30
2 15
输出 #1
1.00
输入 #2
100 2
30 50
90 100
输出 #2
39.05
说明/提示
对于10%的数据:1≤M≤10,1≤Yi≤100;

对于30%的数据:1≤M≤50,1≤Yi≤1,000;

对于80%的数据:1≤M≤500,1≤Yi≤1,000;

对于100%的数据:1≤M≤800,1≤N≤1000,1≤Xi≤N,1≤Yi≤100,000.

【样例解释】

注意,封锁海滩是指,敌人的深入程度是有限制的,若敌人绕过了所有的信号塔,并且可以长驱直入,那么就说明道路没有完全封锁。


本题大意就是寻求到一个每个圆的最小半径,使得左右连通,就是差不多下图的样子
在这里插入图片描述
这道题有点像2017NOIP提高D2T1,不是有点像,几乎差不多。
主要思路是通过二分答案的方法来找到最小半径
我们在判断当前二分到的答案的这个半径的圆相邻的是否可以相切或者相交,那么就用并查集合并这两个圆(什么玩意 )最后判断合并之后的最左边与最右边的圆是否也与边界相切或者相交,如果相切或相交则说明设定这个圆的半径可以使整个图连通起来。

代码:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
struct CZP
{
	int x,y;
	double dis;
}a[1000001];
int fa[10001],f[10001][2],n,m,len;
double js(int x1,int y1,int x2,int y2)
{
	return sqrt(1.0*(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int cmp(CZP a,CZP b)
{
	return a.dis<b.dis;
}
int find(int x)
{
	if (x==fa[x])
	return x;
	else
	fa[x]=find(fa[x]);
	return fa[x];
}  //并查集路径压缩查找操作
void he(int x,int y)
{
	int x1=find(x),y1=find(y);
	if (x1!=y1)
	fa[y1]=x1;
	return ;
}  //并查集合并操作
int check(double k)
{
	for (int i=0;i<n;i++)
	fa[i]=i;  //初始化
	int i=1;
	while (a[i].dis<=2*k && i<=m)
	{
		he(a[i].x,a[i].y);
		i++;   //如果两点之间距离小于等于2*k则说明这两个圆相切或相交,则可以合并起来
	}
	for (int i=0;i<n;i++)
	for (int j=0;j<n;j++)
	if (find(i)==find(j) && f[i][0]-k<0 && f[j][0]+k>len)  //若连通最两边的点能够与边界相连就说明整个图就肯定连通了
	return 1;
	return 0;
}
int main()
{
	scanf("%d%d",&len,&n);
	for (int i=0;i<n;i++)
		scanf("%d%d",&f[i][0],&f[i][1]);
	for (int i=0;i<n;i++)
	for (int j=i+1;j<n;j++)
	{
		m++;
		a[m].x=i;
		a[m].y=j;
		a[m].dis=js(f[i][0],f[i][1],f[j][0],f[j][1]); //将每两个点之间的距离存起来
	}
	sort(a+1,a+m+1,cmp);  //按照距离大小排序,以便后面判断操作
	double L=0.0,R=100000000.0; 
	while (L+0.001<R)  //因为保留两位小数,所以精确度在0.001
	{
		double mid=(L+R)/2;
		if (check(mid))
		{
			R=mid;
		}
		else
		L=mid;
	}
	printf("%.2lf",R);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值