题目大意:
题目链接:
USACO:http://train.usaco.org/usacoprob2?a=TyEfGmq7aAo&S=cowtour
洛谷:https://www.luogu.org/problemnew/show/P1522
有一个无向图,可以在两个不同的联通块中选择其中两个结点并连接,求此时的新联通块的最远两点之间的距离的最小值。
思路:
n
≤
150
n\leq150
n≤150的数据很明显是在提示我们用
F
l
o
y
d
Floyd
Floyd做。那么我们就可以求出任意两点之间的距离。
此时可以选择枚举任意两点
i
,
j
i,j
i,j,如果这两点不在一个联通块(
d
i
s
[
i
]
[
j
]
>
I
n
f
dis[i][j]>Inf
dis[i][j]>Inf),那么就可以
O
(
n
)
O(n)
O(n)求出这两个点和同一联通块的最远点之间的距离,然后更新答案
a
n
s
=
m
i
n
(
a
n
s
,
s
u
m
1
+
s
u
m
2
+
c
a
l
(
i
,
j
)
)
ans=min(ans,sum1+sum2+cal(i,j))
ans=min(ans,sum1+sum2+cal(i,j))
最终取个
6
6
6位小数就可以了。
然后就可以得到
90
90
90分的高分。
此时再打个表就过了
关于# 7 7 7
它死了。
为什么会这样呢?
我们发现,每次更新
a
n
s
ans
ans的时候,是把新联通块的新加入的边看成最长路径中的一条了。那么有没有可能加入这条边之后最长路依然只存在其中的一个旧联通块中呢?
有可能。
那么就得先把所有联通块的最长路求出来,然后再更新
a
n
s
ans
ans的时候再加入两个条件:
a
n
s
=
m
i
n
(
a
n
s
,
m
a
x
(
s
u
m
1
+
s
u
m
2
+
c
a
l
(
i
,
j
)
,
m
a
x
(
s
1
,
s
2
)
)
)
ans=min(ans,max(sum1+sum2+cal(i,j),max(s1,s2)))
ans=min(ans,max(sum1+sum2+cal(i,j),max(s1,s2)))
其中
s
1
,
s
2
s1,s2
s1,s2分别表示新边所连接的两个旧联通块的最长路。
但是这样就必须用并查集了。
时间复杂度:
O
(
n
3
α
(
n
)
)
O(n^3\alpha(n))
O(n3α(n))
代码:
/*
ID:ssl_zyc2
TASK:cowtour
LANG:C++
*/
#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
using namespace std;
const int N=200;
const double Inf=1e9;
int n,x[N],y[N],map[N][N],father[N];
double dis[N][N],sum1,sum2,ans,maxn[N];
double cal(double x1,double x2,double y1,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int find(int x)
{
return x==father[x]?x:father[x]=find(father[x]);
}
int main()
{
freopen("cowtour.in","r",stdin);
freopen("cowtour.out","w",stdout);
ans=Inf;
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d%d",&x[i],&y[i]);
father[i]=i;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
scanf("%1d",&map[i][j]);
if (map[i][j]==1)
{
dis[i][j]=cal(x[i],x[j],y[i],y[j]); //求路径长度
father[find(j)]=find(i);
}
else dis[i][j]=Inf+1.0;
}
for (int k=1;k<=n;k++) //Floyd
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i!=j&&j!=k&&k!=i)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (find(i)==find(j)&&i!=j)
maxn[find(i)]=max(maxn[find(i)],dis[i][j]);
//maxn[i]表示含i的联通块的最长路
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) //枚举任意两点
if (i!=j&&find(i)!=find(j))
{
sum1=0;
sum2=0;
for (int k=1;k<=n;k++)
{
if (find(i)==find(k)&&i!=k)
sum1=max(sum1,dis[i][k]);
if (find(j)==find(k)&&j!=k)
sum2=max(sum2,dis[j][k]);
}
ans=min(ans,max(sum1+sum2+cal(x[i],x[j],y[i],y[j]),max(maxn[find(i)],maxn[find(j)])));
}
printf("%6lf\n",ans);
return 0;
}