1. 程式人生 > >【USACO2.4.3】【洛谷P1522】牛的旅行【最短路】【並查集】

【USACO2.4.3】【洛谷P1522】牛的旅行【最短路】【並查集】

題目大意:

題目連結:

USACO:http://train.usaco.org/usacoprob2?a=TyEfGmq7aAo&S=cowtour
洛谷:https://www.luogu.org/problemnew/show/P1522

有一個無向圖,可以在兩個不同的聯通塊中選擇其中兩個結點並連線,求此時的新聯通塊的最遠兩點之間的距離的最小值。


思路:

n 150

n\leq150 的資料很明顯是在提示我們用 F l o y d Floyd 做。那麼我們就可以求出任意兩點之間的距離。
此時可以選擇列舉任意兩點 i
, j i,j
,如果這兩點不在一個聯通塊( d i s [ i ]
[ j ] > I n f dis[i][j]>Inf
),那麼就可以 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))
最終取個 6 6 位小數就可以了。
然後就可以得到 90 90 分的高分。
此時再打個表就過了


關於# 7 7

它死了。
為什麼會這樣呢?
我們發現,每次更新 a n s ans 的時候,是把新聯通塊的新加入的邊看成最長路徑中的一條了。那麼有沒有可能加入這條邊之後最長路依然只存在其中的一箇舊聯通塊中呢?
有可能。
那麼就得先把所有聯通塊的最長路求出來,然後再更新 a n s 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)))
其中 s 1 , s 2 s1,s2 分別表示新邊所連線的兩個舊聯通塊的最長路。
但是這樣就必須用並查集了。
時間複雜度: O ( n 3 α ( n ) ) O(n^3\alpha(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;
}