#10015. 「一本通 1.2 练习 2」扩散

本文探讨了求解平面点最早形成连通块的问题,介绍了两种算法:floyd+dp和并查集+二分。floyd算法用于确定两点间最短距离,dp用于求解最小时刻;并查集用于确定公共点,二分法缩短距离,最终求出所有点形成连通块的最早时间。

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

目录

   思路【floyd+dp】:

 【代码实现:floyd+dp】

   思路2:并查集+二分

 【代码实现:并查集+二分】 

             最后来个小小的总结:


【题目描述】

一个点每过一个单位时间就会向 4 个方向扩散一个距离,如图所示:两个点 a 、b 连通,记作 e(a,b) 当且仅当 a 、b 的扩散区域有公共部分。连通块的定义是块内的任意两个点 u、v 都必定存在路径 e(u,a0​),e(a0​,a1​),…e(ak​,v)。                                                   给定平面上的 n 个点,问最早什么时候它们形成一个连通块。

【输入格式】

第一行一个数 n ,以下 n 行,每行一个点坐标。

【输出格式】

输出仅一个数,表示最早的时刻所有点形成连通块。

【样例输入】

2
0 0
5 5

【样例输出】

5

【数据范围与提示】

对于 20% 的数据,满足 1≤n≤5,1≤Xi​,Yi​≤50;

对于 100% 的数据,满足 1≤n≤50,1≤Xi​,Yi​≤10^9。

思路【floyd+dp】:

首先我们一看到这道题目,最早的时刻,好的一定是首选二分对吧,但是你再细细看一下,这是一个连通图,一个距离的连通,那就意味着什么?意味着我们可以用最短路来求出最短距离之后在进行判断。跟随我上面讲了,这道题的细节也很重要

设两个点A、B以及坐标分别为

         ,则A和B两点之间的距离为:

以上的式子在代码中不好表示,那就根据数学稍微改变一下:

AB=|X_1{}-X_2{} |+|Y_1{}-Y_2{}|

这样一来我们可以十分方便的把这个式子带入到代码当中(绝对值的表示为abs,记得在前面加头文件

#include<cmath>

这便是我所说的距离, 以后遇到这样的坐标距离题都可以直接用上面的公式。好我们继续,我们求出了点两两之间的距离之后,就是最最最重要的一步,跑floyd,为什么重要呢?因为没有了这一步,你的代码就失败了,floyd是帮助我们确定两个点之间的最短距离的方向,按照最短距离的方向来延伸。
 

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++) if(i!=k)
        for(int j=1;j<=n;j++) if(j!=i && j!=k)
            floyd[i][j]=min(floyd[i][j],floyd[i][k]+floyd[j][k]);

只有四行,三个循环一个计算。记住这个优秀的算法——floyd算法

【代码实现:floyd+dp】

/*
解释一下定义:他是要存在了公共区域才算OK啊
是全部都有公共区域 
而且要求最小的时刻,那就意味着我们要让所有点之间的距离尽可能的小
在这个方面的基础上,最有效的办法就是就是求任意两点直接最短路的算法
dijkstra算法 / spfa算法 / floyd算法 都是有效的最短路算法 
*/
#include<cmath>
#include<cstdio>
#include<cstdio>
#include<algorithm>
using namespace std;
int x[55],y[55];
int w[55][55];//记录的是两个点之间的距离 
int main()
{
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			w[i][j]=w[j][i]=abs(x[i]-x[j])+abs(y[i]-y[j]);//求出两点的距离 
			//距离公式是abs(x0 - x1) + abs(y0 +y1),
	//经过多次画图之后知道,其实每一次的扩展就是在外面多一圈 
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				w[i][j]=min(w[i][j],max(w[i][k],w[k][j]));
	/*
	寻找最短距离 
	仔细一看有点像floyd算法,求任意两点最短路的算法
	floyd的精髓所在就是我们用三个循环,然后就可以判断出
	他到底是直接到距离最短,还是经过其他点相加的路程距离最短 
	所以这一步是不可以没有的,我们最先找到的距离公式只是片面的
	floyd才是最根本的一个距离的记录,我也发现把这一部分删掉就会wa掉 
	*/ 
	int ans=0;//记录最小的时刻 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			ans=max(ans,w[i][j]);//ans就是更大的距离 
	printf("%d\n",(ans+1)/2); 
	/*
		两个点连通仅当距离<=时间*2 这是推理的一条公式
		然后根据这条公式我们可以知道,w[i][j]记录的就是距离
		而ans是最大距离的记录成立的跑过floyd之后最短的最大距离
		(之所以要找到最大是因为我们要让所有都连通,那就要兼顾最坏的情况) 
		所以的话,我们的ans要小于等于时间乘以2
		那么知道距离,时间就是除以2
		然后这个+1是为了防止出错,怎么个出错法呢?
		就是那些交叉的,其实差一点点才能够联通
		但是按照我们的算法电脑默认连通其实只是衔接上没有算得上是连通
		这是时候就要加多一步来实现
		(最主要的是双数的时候没有影响,只是影响单数的衔接情况) 
	*/ 
	return 0;
}
//https://blog.csdn.net/xianpingping/article/details/79947091
//优秀的floyd算法的解释博客 

思路2:并查集+二分

首先我们为什么要用并查集,因为要确定一个两个点延伸之后的公共点,我们把这个公共点通过并查集找到,定义为他们的父亲节点。接着就是二分来缩短距离。其实二分也很简单,就是说实在的吧,我想到了二分,但是我没有想到并查集,所以才会放弃二分转战floyd。经过这一经历,我觉得我对并查集产生了一种好感,(原来并查集可以找距离,这个很震撼)

【代码实现:并查集+二分】 

#include<cstdio>
#include<cstring>
#include<algorithm> 
using namespace std;
struct node
{
	int x,y;
}a[55];
int n,d[55][55],fa[55];//d[i][j]记录距离,fa记录父亲 
inline int abs(int x){return x>0?x:-x;}
int juli(node n1,node n2){return abs(n1.x-n2.x)+abs(n1.y-n2.y);}
int find(int x)
{
	if(x==fa[x])return x;
	else return fa[x]=find(fa[x]);
}
bool check(int x)
{
	for(int i=1;i<=n;i++)fa[i]=i;//先规定好每个点的父亲节点 
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			if(d[i][j]<=x*2)//同样的距离小于两倍的时间 
			{
				int fx=find(i),fy=find(j);
				//先找到自己共同的祖先,就是要相交的时候到达的点 
				if(fx!=fy)fa[fx]=fy;
				//如果不一样的话,将其中一个与另一个变为一样的 
			}
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i)sum++;//祖先相同的话,这样的走向就成立 
		if(sum==2)return false;
		/*如果有两个祖先的话,说明最短距离没有找对,
		因为只能允许一个最近的祖先*/ 
	}
	return true;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			d[i][j]=d[j][i]=juli(a[i],a[j]);
	int l=0,r=999999999,ans=0;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(check(mid)==true){r=mid-1;ans=mid;}
		//目的求最小,正确往左移 
		else l=mid+1;
	}
	printf("%d\n",ans);
	return 0;
}

最后来个小小的总结:

可以求距离的算法(或者说和距离有关系的)

spfa算法(bfs

dijkstra算法(单源最短路径算法)

floyd算法(任意点之间的最短路)

并查集(找到一个共同目标为父亲节点)

目前我找到的最有效最好用的就是这四个了

 

### C++ `do...while` 循环的经典练习题 以下是几个经典的基于 `do...while` 的 C++ 练习题目,这些题目可以帮助初学者更好地理解并掌握该循环结构。 #### 题目一:猜数字游戏 编写一个程序,让用户猜测预设的一个秘密数字(例如 13)。如果用户的输入不等于这个秘密数字,则提示用户重新输入;直到用户猜中为止。 ```cpp #include <iostream> using namespace std; int main() { const int secret_number = 13; int user_guess; do { cout << "请输入您猜测的数字: "; cin >> user_guess; if (user_guess != secret_number && user_guess > 0) { cout << "错误! 请再次尝试." << endl; } } while (user_guess != secret_number); cout << "恭喜您,猜对了!" << endl; return 0; } ``` 此代码展示了如何利用 `do...while` 实现至少执行一次循环的功能[^2]。 --- #### 题目二:计算累加和 要求用户连续输入若干正整数,并将其累加求和。当用户输入负数时结束输入过程,并显示总和。 ```cpp #include <iostream> using namespace std; int main() { int number, sum = 0; do { cout << "请输入一个正整数(输入负数结束): "; cin >> number; if (number >= 0) { sum += number; } } while (number >= 0); cout << "所有正整数的累加和为: " << sum << endl; return 0; } ``` 这段代码过 `do...while` 结构实现了动态终止条件下的数据处理逻辑[^3]。 --- #### 题目三:验证密码合法性 假设有一个固定的有效密码字符串 `"password"`,要求用户不断输入密码直至匹配成功才允许继续操作。 ```cpp #include <iostream> #include <string> using namespace std; int main() { string password, input_password; bool is_correct = false; cout << "设置有效密码为 &#39;password&#39;" << endl; do { cout << "请输入您的密码: "; cin >> input_password; if (input_password == password) { is_correct = true; } else { cout << "密码错误,请重试..." << endl; } } while (!is_correct); cout << "欢迎登录系统!" << endl; return 0; } ``` 这里体现了 `do...while` 对于需要反复确认某项任务完成情况的应用场景的价值[^1]。 --- #### 题目四:水仙花数查找器 定义水仙花数是指一个三位数,其各位数字立方之和正好等于该数本身。例如 \(153\) 是水仙花数因为 \(1^3 + 5^3 + 3^3 = 153\) 。下面是一个简单的实现方式来找出所有的三位水仙花数。 ```cpp #include <iostream> using namespace std; bool isNarcissistic(int n){ int originalNumber=n,sum=0,digitCount=0,temp=n; // 计算位数 while(temp>0){ digitCount++; temp /=10 ; } temp =n ; // 判断是否为水仙花数 while(temp>0 ){ int remainder=temp%10 ; sum+=pow((double )remainder ,digitCount ); temp/=10 ; } return sum==originalNumber ; } int main(){ int count=0 ; for(int i=100 ;i<=999 ;i++){ if(isNarcissistic(i)){ cout<<i<<" 是水仙花数"<<endl ; count++ ; } } cout<<"\n总共找到 "<<count<<" 个水仙花数 ."<<endl ; return 0 ; } ``` 上述例子结合了函数封装以及多层嵌套控制流语句共同完成了目标功能开发工作流程说明. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值