*PS:竟然CCCCCE了(눈‸눈)
Description
聰聰研究發現,荒島野人總是過着羣居的生活,但是,並不是整個荒島上的所有野人都屬於同一個部落,野人們總是拉幫結派形成屬於自己的部落,不同的部落之間則經常發生爭鬥。只是,這一切都成爲謎團了——聰聰根本就不知道部落究竟是如何分佈的。 不過好消息是,聰聰得到了一份荒島的地圖。地圖上標註了N個野人居住的地點(可以看作是平面上的座標)。我們知道,同一個部落的野人總是生活在附近。我們把兩個部落的距離,定義爲部落中距離最近的那兩個居住點的距離。聰聰還獲得了一個有意義的信息——這些野人總共被分爲了K個部落!這真是個好消息。聰聰希望從這些信息裏挖掘出所有部落的詳細信息。他正在嘗試這樣一種算法: 對於任意一種部落劃分的方法,都能夠求出兩個部落之間的距離,聰聰希望求出一種部落劃分的方法,使靠得最近的兩個部落儘可能遠離。 例如,下面的左圖表示了一個好的劃分,而右圖則不是。請你編程幫助聰聰解決這個難題。
Input
第一行包含兩個整數N和K(1< = N < = 1000,1< K < = N),分別代表了野人居住點的數量和部落的數量。
接下來N行,每行包含兩個正整數x,y,描述了一個居住點的座標(0 < =x, y < =10000)
Output
輸出一行,爲最優劃分時,最近的兩個部落的距離,精確到小數點後兩位。
Sample Input
4 2
0 0
0 1
1 1
1 0
Sample Output
1.00
Solution 1 二分答案+並查集驗證
二分兩個最近部落的距離,對於距離小於mid的兩個點就把他們合併,最後統計一下聯通塊個數是否大於等於k,true就調整右邊界,不然就調整左邊界。
代碼
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int N,K;
int fa[1010],used[1010];
double r=-1;
double a[1010][1010];
struct maple{
int x,y;
}ship[1010];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
bool can(double q)
{
int k=0;
for(int i=1;i<=N;++i) fa[i]=i,used[i]=0;
for(int i=1;i<=N;++i)
for(int j=i+1;j<=N;++j)
if(a[i][j]<q)
{
int x=find(i),y=find(j);
if(x!=y) fa[x]=y;
}
for(int i=1;i<=N;++i)
{
int x=find(i);
if(!used[x])
{
used[x]=1;
++k;
}
}
if(k>=K) return true;
else return false;
}
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;++i)
scanf("%d%d",&ship[i].x,&ship[i].y);
for(int i=1;i<=N;++i)
for(int j=i+1;j<=N;++j)
{
a[i][j]=sqrt(pow((double)ship[i].x-ship[j].x,2)+pow((double)ship[i].y-ship[j].y,2));//強轉,不然會ce
r=max(r,a[i][j]);
}
double l=0;
while(l+0.0001<r) //0.001 會wa
{
double mid=(l+r)/2;
if(can(mid)) l=mid;
else r=mid;
}
printf("%.2lf",l);
return 0;
}
Solution 2 最小生成樹
(摘自題解,侵刪)
首先處理出任意兩點間的距離,這時的答案就是其中最短的距離,我們把距離最短的兩點合併到一個集合(連邊),此時答案又變成剩餘的距離中的最短距離,我們再次進行連接……可知答案一定在最小生成樹中。
所以我們可以建立一棵最小生成樹,來保證每次連邊都是不在同一個集合內的兩點且之間的距離爲當前最小的,直到連接了n-k+1條邊。
至於爲什麼是n-k+1條??
n-k是爲了保證能夠建成k個集合(已知n-1條邊是一棵樹,若刪去一條邊–>n-2是兩棵樹……那麼n-k就是k棵樹)
+1的原因是爲了保證當前求的這一條邊是集合外的邊,並不是連接的意思。
這樣第n-k+1條邊的長度就是答案。