[并查集 + 二分]P1783 海滩防御 题解

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;
}

posted @ 2025-06-24 23:24  「癔症」  阅读(6)  评论(0)    收藏  举报