小蔥同學今天學會了複製,於是我們現在有N個小蔥同學。N個小蔥同學每人有一把彈弓,並且第i個小蔥同學站在(\(x_i, y_i\))的位置上。現在所有的小蔥同學希望把彈弓的射程調到某一個值,使得在這個射程下,存在M個小蔥同學,這M個小蔥同學之之間任意兩位都可以互相射擊到。問這個距離最小是多少。
【輸入格式】
第一行兩個數N,M
接下來N行每行兩個整數代表小蔥同學的座標
【輸出格式】
輸出一行一個六位小數代表答案
【樣例】
4 3
0 0
0 1
1 1
1 0
【輸出】
1.414212
【數據規模與約定】
對於30%的數據,\(N \leq 10\)
對於另外10%的數據,\(M = 2\)
對於另外20%的數據,\(M = 3\)
對於80%的數據,\(M \leq 50\)
對於100%的數據,\(1 \leq M \leq N \leq 200, |x|, |y| \leq 10^4\)
Solution
看題意似乎是讓我們求一個最大團,但這在普通圖裏是個NPC問題,那有什麼特殊圖能做嗎?
二分圖可以。因爲二分圖相當於兩個點集,每個點集之內的點相互之間都有邊,兩個點集之間又有一些邊。所有就相當於要找一個最大匹配。這個顯然可以求。
但是,哪裏來的二分圖呢?
考慮這是一個座標圖,任意兩點的連線會有上面的一部分和下面的一部分。當我們選中兩個點,並以兩個點的距離爲半徑畫圓的時候,這兩個圓會有一個相交的集合。將這個集合分成兩半,一半在兩點連線的上面,一半在下面。這兩個點集的內部的點的距離一定小於等於選中兩點之間的距離,而兩個點集之間的點不一定。所以這就形成了一個二分圖。
至於怎麼判斷某個點是屬於上面一個集合還是下面的,用向量的叉乘就好了。
所有,我們首先二分一個距離,然後找到所有滿足距離小於等於這個距離的點對,再以每個點對畫圓,構建二分圖,統計每個二分圖的最大匹配。如果這個匹配大於等於M,就說明OK,反之不行。
這裏要注意一點,二分圖不能直接求最大匹配,因爲我們實際上要求的是一個最大團,所以要求一個反圖的最大匹配。最大團 = 反圖最大獨立集 = 總點數 - 反圖最大匹配
這題就這樣做完了qwq
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int n, m, l, r, p[2][210], re[210], x[210], y[210];
bool use[210];
int c, dis[210][210], d[210 * 210];
inline int cross(int x1, int y1, int x2, int y2) {//叉乘
return x1 * y2 - x2 * y1;
}
inline bool dfs(int now) {//求最大匹配
for (int i = 1; i <= r; ++i)
if (!use[i] && dis[p[0][now]][p[1][i]] > c) {//因爲是反圖,所以找距離大於c的
use[i] = 1;
if (!re[i] || dfs(re[i])) {
re[i] = now; return 1;
}
}
return 0;
}
inline bool check(int d) {
if (m == 1) return 1;
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
if (dis[i][j] <= d) {
if (m == 2) return 1;
l = 0, r = 0;
for (int k = 1; k <= n; ++k)
if (k != i && k != j && dis[i][k] <= dis[i][j] && dis[j][k] <= dis[i][j]) {
//判斷這個節點是否在兩個圓的交集內,並劃分集合
if (cross(x[k] - x[i], y[k] - y[i], x[j] - x[i], y[j] - y[i]) <= 0) p[0][++l] = k;
else p[1][++r] = k;
}
c = d;
memset(re, 0, sizeof re);
int ans = 0;
for (int k = 1; k <= l; ++k) {//求匹配
memset(use, 0, sizeof use);
if (dfs(k)) ans++;
}
ans = l + r - ans + 2;//統計答案
if (ans >= m) return 1;
}
return 0;
}
int main() {
freopen("die.in", "r", stdin);
freopen("die.out", "w", stdout);
scanf("%d%d", &n, &m);
if (m == 1) {
printf("0.000000\n"); return 0;
}
for (int i = 1; i <= n; ++i)
scanf("%d%d", &x[i], &y[i]);
int cnt = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {//計算出兩點之間的距離
dis[i][j] = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
//直接存儲平方
d[++cnt] = dis[i][j];//二分的數組
}
sort(d + 1, d + 1 + cnt);
int l = 0, r = cnt, u = cnt;
while (l < r) {
int mid = (l + r) >> 1;
if (check(d[mid])) u = mid, r = mid - 1;
else l = mid + 1;
}
printf("%.6lf\n", sqrt(d[u]));
return 0;
}