一个点每过一个单位时间就会向四个方向扩散一个距离,如图。
两个点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
5
#include
using namespace std;
struct node{
int a,b;
}q[1000];
istream &operator >> (istream& in,node& x){
in>>x.a>>x.b;return in;
}
inline int calc(node x,node y){
int ad=abs(x.a-y.a),bd=abs(x.b-y.b);
return ceil((ad+bd)/2.0);
}
int n,dis[1000][1000];
int main(){
cin>>n;int ans=-1e10;
for (int i=1;i<=n;i++)
cin>>q[i];
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
int tmp=calc(q[i],q[j]);
dis[i][j]=tmp;
}
}
for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
dis[i][j]=min(max(dis[i][k],dis[k][j]),dis[i][j]);
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
ans=max(ans,dis[i][j]);
}
cout<<ans;
return 0;
}
我们分着来看:
1.node
struct node{
int a,b;
};
istream &operator >> (istream& in,node& x){
in>>x.a>>x.b;return in;
}
a,b表示一组坐标,通过对>>
的运算符重载实现对node的输入
我们考虑有两个点A( x 1 x_1 x1, y 1 y_1 y1),B( x 2 x_2 x2, y 2 y_2 y2)
那么从A到B(不考虑中间有别的点)的扩散所需时间为
t = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ 2 t={|x_1-x_2|+|y_1-y_2|\over2} t=2∣x1−x2∣+∣y1−y2∣
注意, ∣ x 1 − x 2 ∣ + ∣ y 1 + y 2 ∣ {|x_1-x_2|+|y_1+y_2|} ∣x1−x2∣+∣y1+y2∣是一个偶数是才适用,由于 i n t int int型的浮点会直接舍去,所以可以转为 d o u b l e double double使用ceil()函数,那么公式为:
t = ⌈ ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ 2 ⌉ t=\left\lceil\frac{|x_1-x_2|+|y_1-y_2|}{2}\right\rceil t=⌈2∣x1−x2∣+∣y1−y2∣⌉
代码为
inline int calc(node x,node y){
int ad=abs(x.a-y.a),bd=abs(x.b-y.b);
return ceil((ad+bd)/2.0);
}
2.建图
我们需表示出每个点到其他点的权,使权值为 t t t(见上文):
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
int tmp=calc(q[i],q[j]);
dis[i][j]=tmp;
}
}
利用calc
求出 i i i到 j j j的权,建图存在dis数组中
3.floyd
代码的核心部分,利用了floyd算法的思想,我们设想有一 k k k位于 i i i与 j j j之间,由于 k k k的扩散, i i i到 j j j的权不一定是 t t t了,上三点同时扩散,假设 i i i先与 k k k连通,那么三点同时连通的时间(图上的权)为 d i s [ k ] [ j ] dis[k][j] dis[k][j],反之为 d i s [ i ] [ k ] dis[i][k] dis[i][k],数学表达式为:
d i s n e w = max { d i s [ i ] [ k ] , d i s [ k ] [ j ] } dis_{new}=\max\{dis[i][k],dis[k][j]\} disnew=max{dis[i][k],dis[k][j]}
但又有可能在三点时连通前 i i i与 j j j先一步连通,所以:
d i s [ i ] [ j ] = min { d i s [ i ] [ j ] , d i s n e w } dis[i][j]=\min\{dis[i][j],dis_{new}\} dis[i][j]=min{dis[i][j],disnew}
即:
d i s [ i ] [ j ] = min { d i s [ i ] [ j ] , max { d i s [ i ] [ k ] , d i s [ k ] [ j ] } } dis[i][j]=\min\{dis[i][j],\max\{dis[i][k],dis[k][j]\}\} dis[i][j]=min{dis[i][j],max{dis[i][k],dis[k][j]}}
代码:
for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
dis[i][j]=min(max(dis[i][k],dis[k][j]),dis[i][j]);
}
}
4.输出
在最后,我们要输出答案,当最后两点连通时,便形成连通块了
代码
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
ans=max(ans,dis[i][j]);
}
由于ans初始化为一个负数,最终ans就是答案
#include
using namespace std;
struct node{
int a,b;
}q[1000];
istream &operator >> (istream& in,node& x){
in>>x.a>>x.b;return in;
}
inline int calc(node x,node y){
int ad=abs(x.a-y.a),bd=abs(x.b-y.b);
return ceil((ad+bd)/2.0);
}
int n,u[100000],v[100000],w[100000],r[100000],p[100000];
bool cmp(const int i,const int j){return w[i]<w[j];}
int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
int main(){
cin>>n;int ans=0;
for (int i=1;i<=n;i++)
cin>>q[i];
int cnt=0;
for (int i=0;i<n;i++) p[i]=i;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (i==j) continue;
int tmp=calc(q[i],q[j]);
u[cnt]=i;v[cnt]=j;w[cnt]=tmp;r[cnt]=cnt;
cnt++;
}
}
sort(r,r+cnt,cmp);
for (int i=0;i<cnt;i++){
int e=r[i];int x=find(u[e]),y=find(v[e]);
if (x!=y){
ans=w[e];p[x]=y;
}
}
cout<<ans;
return 0;
}
比起Floyd算法,Kruskal更难理解一些吧
1.并查集与p数组
并查集 讲的不好,看不懂看其他人的吧
顾名思义,其分为并
与查
查
:比如:
这虽然是一棵树,我们设数组a来存它的元素
int a[]={123,342,312,423,521,62,721};
我们只考虑父节点,用p数组存储元素num下标i的父节点j,即:
a [ i ] = n u m ; p [ i ] = j a[i]=num\ ;\ p[i]=j a[i]=num ; p[i]=j
例如:62的下标为5,423的下标为3,p[5]=3;
int p[]={0,0,0,0,1,3,3};
不难发现,所有元素的根节点均是123,下标为0,我们可以说,这个集合叫0,所有元素均属于0;
这是只有一个集合,我们看下面的集合:
为了便于理解,我们使元素与下标相同
int a[]={0,1,2,3,4,5,6};
int p[]={0,0,0,3,1,3,3};
现在有了0和3两个集合,此时便要查找某个元素属于哪个集合了,比如6属于集合3,1属于集合0,我们很容易想出用递归来做:
int find(int x){
if (p[x]==x) return x
else return find(p[x]);
}
可以这样做,但数据很大时,需递归多次,如
查一次4用到了find(3),查3也用到了find(3),效率低下
但这个集合与它是相同的:
很显然,这个集合中的任意元素调用find()的时不再递归,结果也相同,效率也高,这种方法叫路径压缩
代码:
int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
那么并
呢?
我们要把a所在集合,与b所在集合合并,那么可以
p[find(a)]=find(b);
把"根节点"的p数组改下就好了
2.图的数组
int u[100000],v[100000],w[100000];
u[i],v[i]表示第i条边的起点与终点,w[i]表示第i条边的权
建图代码:(r的作用后面解释)
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (i==j) continue;
int tmp=calc(q[i],q[j]);
u[cnt]=i;v[cnt]=j;w[cnt]=tmp;r[cnt]=cnt;
cnt++;
}
}
3.间接排序
根据Kruskal的思想,我们把所有边表示出来,从最小的边开始选,只要不构成环就选,我们要先进行排序,定义数组r,第i小的边的序号存储在r[i],通过对r的修改,表示出我们对边处理的顺序,这需要间接排序,
代码:
bool cmp(const int i,const int j){return w[i]<w[j];}
int main(){
sort(r,r+cnt,cmp);
return 0;
}
sort对r进行排序,但依据是w的大小
4.Kruskal
代码的核心
代码:
for (int i=0;i<cnt;i++){
int e=r[i];int x=find(u[e]),y=find(v[e]);
if (x!=y){
ans=w[e];p[x]=y;
}
}
p[x]=y;
实现了并集;
重要,若求最小生成树的权值和,应写为ans+=w[e],但点是同时扩散的,故应输出最长边,改为ans=w[e];
由于已排好序,故输出ans即可;