题目描述
一个点每过一个单位时间就会向四个方向扩散一个距离,如图。

两个点a、b连通,记作e(a,b),当且仅当a、b的扩散区域有公共部分。连通块的定义是块内的任意两个点u、v都必定存在路径e(u,a0),e(a0,a1),…,e(ak,v)。给定平面上的n给点,问最早什么时刻它们形成一个连通块。
输入格式
第一行一个数n,以下n行,每行一个点坐标。
数据规模
对于20%的数据,满足1≤N≤5; 1≤X[i],Y[i]≤50;
对于100%的数据,满足1≤N≤50; 1≤X[i],Y[i]≤10^9。
输出格式
一个数,表示最早的时刻所有点形成连通块。
输入输出样例
2
0 0
5 5
输出 #1
5
事实上,看到题目以后,尽管知道是二分的思路,然鹅我还是不知道该从何下手...(;´ρ`)
后来在网上看到了这位大神的题解,感觉真是豁然开朗,我把两种代码都写了一遍,并分别在ybt和洛谷上提交了,并且全部AC了,感谢大佬提供的思路!
点击即可查看大神思路(真的佩服得五体投地)
floyd+dp
首先我们仔细审一遍题——emm,连通块、任意两点间路径……诶好像有点最短路径的想法!
我们可以这样想,任意两点间的最短距离,由于单位时间内扩散一个单位距离,所以其实也就是两点连通的最短时间的两倍(两点同时扩散)
这里可以用floyd,并不会超时,但是需要注意因为扩散是所有点同时进行的,所以两点之间的距离(d[i][j])推导式要进行一点点变形:
d[i][j]=min(d[i][j],max(d[i][k],d[k][j]));(应该很好理解吧(* ̄︶ ̄))
在进行预处理的时候,任意两点间的距离
但也可以直接
这样在代码中更好运用
最后用两层循环求出图中两点间的 最大的 最短距离 即可,但这还不是答案啦(*๓´╰╯`๓),真正的答案需要再+1、/2,因为这道沙雕题目的设定是——当两点扩散的范围交叉于一点时,是不算“衔接”的,所以还要加上一才行,至于除以二之前已经说过啦
![]()
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N=1e6+5;
4 int x[N],y[N],fa[N],d[100][100];
5 int n;
6 int m,q,ans,p,tot;
7 int read()
8 {
9 int f=1;char ch;
10 while((ch=getchar())<'0'||ch>'9')
11 if(ch=='-')f=-1;
12 int res=ch-'0';
13 while((ch=getchar())>='0'&&ch<='9')
14 res=res*10+ch-'0';
15 return res*f;
16 }
17 void write(int x)
18 {
19 if(x<0)
20 {
21 putchar('-');
22 x=-x;
23 }
24 if(x>9)write(x/10);
25 putchar(x%10+'0');
26 }
27
28
29 int main()
30 {
31 n=read();
32 for(int i=1;i<=n;i++)
33 x[i]=read(),y[i]=read();
34 for(int i=1;i<n;i++)
35 for(int j=i+1;j<=n;j++)
36 d[i][j]=d[j][i]=abs(x[i]-x[j])+abs(y[i]-y[j]);
37 for(int k=1;k<=n;k++)
38 for(int i=1;i<=n;i++)
39 if(k!=i)
40 for(int j=1;j<=n;j++)
41 if(j!=k&&j!=i)
42 d[i][j]=min(d[i][j],max(d[i][k],d[k][j]));
43 for(int i=1;i<n;i++)
44 for(int j=i+1;j<=n;j++)
45 ans=max(ans,d[i][j]);
46 write((ans+1)/2);
47 return 0;
48 }
floyd+dp 代码
并查集+二分
欸看到上面的“两点间的 最大的 最短距离 ” 是不是感jio很熟悉鸭,这就是肥肠典型的二分答案模型啦
所以这道题肯定可以用二分的方法去做,但是关键就在于该用什么来实现我们二分答案的check咧?
之前提到的那位大神就想出了“并查集”的方法(不得不说,真是太妙了)!
每当我们二分出一个mid值时,都用两个循环先判断两点之间距离是否小于两倍的时间,若是就并在同一个集合里。
注意在check函数中要先将每个点的father赋值为自己本身。
判断结束之后就可以再用一次循环遍历每个点,一旦出现有两个点father值都为自己本身(就说明mid不能满足使所有点都在这个时间内连通),就可以直接返回false,将之前二分的 l 变成mid+1,增大时间;
但是如果满足只有一个点的father是其本身的话,那就可以返回true,将二分边界 r 变为mid-1(还得用一个ans变量来存储最后的答案)
![]()
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N=1e6+5;
4 int x[N],y[N],fa[N],d[100][100];
5 int n;
6 int m,q,w,ans,p,tot;
7 int read()
8 {
9 int f=1;char ch;
10 while((ch=getchar())<'0'||ch>'9')
11 if(ch=='-')f=-1;
12 int res=ch-'0';
13 while((ch=getchar())>='0'&&ch<='9')
14 res=res*10+ch-'0';
15 return res*f;
16 }
17 void write(int x)
18 {
19 if(x<0)
20 {
21 putchar('-');
22 x=-x;
23 }
24 if(x>9)write(x/10);
25 putchar(x%10+'0');
26 }
27 int find(int x)
28 {
29 if(fa[x]!=x)fa[x]=find(fa[x]);
30 return fa[x];
31 }
32 bool check(int t)
33 {
34 for(int i=1;i<=n;i++)fa[i]=i;
35 for(int i=1;i<n;i++)
36 for(int j=i+1;j<=n;j++)
37 if(d[i][j]<=t*2)
38 {
39 int fx=find(i),fy=find(j);
40 if(fx!=fy)fa[fy]=fx;
41 }
42 int sum=0;
43 for(int i=1;i<=n;i++)
44 {
45 if(fa[i]==i)sum++;
46 if(sum==2)return false;
47 }
48 return true;
49 }
50 int main()
51 {
52 n=read();
53 for(int i=1;i<=n;i++)
54 x[i]=read(),y[i]=read();
55 for(int i=1;i<n;i++)
56 for(int j=i+1;j<=n;j++)
57 d[i][j]=d[j][i]=abs(x[i]-x[j])+abs(y[i]-y[j]);
58 int l=0,r=999999999;
59 while(l<=r)
60 {
61 int mid=l+r>>1;
62 if(check(mid))r=mid-1,ans=mid;
63 else l=mid+1;
64 }
65 write(ans);
66 return 0;
67 }
并查集+二分 代码