[并查集 + 二分]P1783 海滩防御 题解
简化题意,就是给你 \(m\) 个点,让你以这 \(m\) 个点为圆心以一定半径长度画圆,求让这 \(m\) 个园能正好堵住长度为 \(n\) 的缝隙的最小半径(感性理解)
首先能想到二分半径长度。检查一下发现复杂度是 \(O(m^2\log W)\) ,完全没问题,那么暴力计算就可以。
计算的过程中,我们可以使用并查集来根据是否有交集把这些圆分成一个一个集合。这就相当于我们把这一堆圆给拆成了一个一个组合来尝试。那么我们只需要看是否能覆盖边界即可。
#include <bits/stdc++.h>
constexpr double eps = 1e-3;
constexpr int M = 805;
constexpr int N = 1e3 + 5;
using namespace std;
int n , m;
double l , r , mid , ans , fa[M] , M1[M] , M2[M];
struct Point {
double x , y;
}arr[M];
inline double dis(int a , int b) {
return __builtin_sqrt((arr[a].x - arr[b].x) * (arr[a].x - arr[b].x) + (arr[a].y - arr[b].y) * (arr[a].y - arr[b].y));
}
inline int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void merge(int a , int b) {
fa[find(a)] = find(b);
}
inline bool check(double num) {
for(register int i = 1; i <= m; ++i) {
fa[i] = i , M1[i] = M2[i] = 1145141919810;
}
for(register int i = 1; i <= m; ++i) {
for(register int j = 1; j <= m; ++j) {
if(i == j) {
continue;
}
if((dis(i , j) <= num * 2) && (find(i) != find(j))) {
merge(i , j);//内部合并集合,把有交集的圆都放在一个集合
}
}
}
for(register int i = 1; i <= m; ++i) {
int j = find(i);
M1[j] = min(M1[j] , arr[i].x) , M2[j] = min(M2[j] , (double)n - arr[i].x);
if(M1[j] <= num && M2[j] <= num) {
return true;//如果一个集合可以覆盖边界,那就说明这个集合可以堵住长度为 n 的缝隙
}
}
return false;
}
int main() {
cin >> n >> m;
for(register int i = 1; i <= m; ++i) {
cin >> arr[i].x >> arr[i].y;
}
l = 0.01 , r = 1e5;
while(r - l >= eps) {
mid = (l + r) / 2;
if(check(mid)) {
r = mid;
}
else {
l = mid;
}
}
cout << fixed << setprecision(2) << mid;
return 0;
}