1. 程式人生 > >[HAOI2006]聰明的猴子 題解

[HAOI2006]聰明的猴子 題解

題意:

在一個熱帶雨林中生存著一群猴子,它們以樹上的果子為生。昨天下了一場大雨,現在雨過天晴,但整個雨林的地表還是被大水淹沒著,部分植物的樹冠露在水面上。猴子不會游泳,但跳躍能力比較強,它們仍然可以在露出水面的不同樹冠上來回穿梭,以找到喜歡吃的果實。

現在,在這個地區露出水面的有N棵樹,假設每棵樹本身的直徑都很小,可以忽略不計。我們在這塊區域上建立直角座標系,則每一棵樹的位置由其所對應的座標表示(任意兩棵樹的座標都不相同)。

在這個地區住著的猴子有M個,下雨時,它們都躲到了茂密高大的樹冠中,沒有被大水沖走。由於各個猴子的年齡不同、身體素質不同,它們跳躍的能力不同。有的猴子跳躍的距離比較遠(當然也可以跳到較近的樹上),而有些猴子跳躍的距離就比較近。這些猴子非常聰明,它們通過目測就可以準確地判斷出自己能否跳到對面的樹上。

【問題】現已知猴子的數量及每一個猴子的最大跳躍距離,還知道露出水面的每一棵樹的座標,你的任務是統計有多少個猴子可以在這個地區露出水面的所有樹冠上覓食。

分析

首先可以把所有樹冠之間兩兩計算距離,時間複雜度(500000左右,足夠)。然後跑一邊Kruskal算出最小生成樹,再在最小生成樹中找到最大的一條邊權,記錄下來和每一隻猴子的跳躍最值相比較就行了。

下面上程式碼

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int fa[1005],a[505],x[1005],y[1005];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}//找父親(寫法一)
/*int find(int x)//找父親(寫法二)
{
	if(fa[x]!=x)fa[x]=find(fa[x]);
	return fa[x];
}*/
struct ben
{
	int s,t,e;
}ans[600005];//定義一個結構體,ans【i】表示一條從s出發到t權值為e的邊
int cmp(const ben &a,const ben &b)//比較大小函式
{
	return a.e<b.e;
}
int op(int a,int b)//連邊函式
{
	int x=find(a);
	int y=find(b);
	if(x!=y)
	fa[y]=x;
	return 0;
}
int main()
{
	int n,m;
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&a[i]);
	}
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x[i],&y[i]);
		fa[i]=i;//順帶著初始化fa陣列,使每個人的父親都先指向自己
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			ans[++cnt].s=i;
			ans[cnt].t=j;
			ans[cnt].e=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));//計算平面直角座標系中兩點距離。
		}
	}
	sort(ans+1,ans+cnt+1,cmp);//從小到大排序,貪心思想
	int sum=0;
	int anss=0;
	for(int i=1;i<=cnt;i++)
	{
		if(find(ans[i].s)!=find(ans[i].t))//判斷是否已經有邊
		{
			op(ans[i].s,ans[i].t);//連邊
			anss=fmax(anss,ans[i].e);//不停取最大值,準備最後比較
			sum++;//記錄連了幾條邊
		}
		if(sum==n-1)//最小生成樹比然有節點個數-1條邊
		break;
	}
	int answer=0;
	for(int i=1;i<=m;i++)
	{
		if(a[i]>=anss)//每隻猴子的跳躍極限只要超過了最小生成樹中最長的一條邊就一定能跳到所有節點
		{
			answer++;
		}
	}
	printf("%d",answer);
	return 0;
}