并查集 + 二分
我是 并查集 + 二分 做的QVQ
思路:两两枚举点之间的距离,sort排序,使距离有序。二分答案,每次判断是否符合条件,然后缩小查询范围,直到满足题目要求(保留2位小数精度就为 0.001就好了)最后保留两位小数输出
核心 判断是否符合条件:
对于每次判断,首先应初始化并查集。因为距离有序,一直并集直到两点距离大于(要判断的)k的两倍(k为每次二分检查的半径,两点距离大于半径即两点相离)为止。
(现在所有距离小于k*2 都被联通了)两两枚举所有点,若有一点i离左海岸线的距离<k,说明点i在半径为k的条件下能够到左边界;同理,若有一点j离右海岸线的距离 + k > len说明点j在半径为k的条件下够得到右边界,如果i,j在同一集合内,说明成立(左边界->i->j->右边界;满足封锁)
需要注意的是:
并查集初始化需从0开始(读题);
每次判断成立都要初始化并查集;
代码如下(注释并上):
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn = 1001100;
int num,nr;//点数,边数
int len;//海滩宽度
int father[maxn];//并查集
struct R{
int u,v;
double dis;
}I[maxn];//边
struct Node{
int x,y;
}N[maxn];//点
bool cmp(R a,R b){
return a.dis < b.dis;//sort的cmp函数
}
int findfather(int v){//查找祖先(注意压缩路径【不然也许会超时QAQ】)
if(father[v] == v)return v;
int F = findfather(father[v]);
father[v] = F;
return F;
}
void Union(int a,int b){//并集
int faA = findfather(a);
int faB = findfather(b);
if(faA != faB){
father[faA] = faB;
}
}
bool check(double k){//判断函数
for(int i = 0;i < num;i++)father[i] = i;//并查集每次初始化
int i = 1;
while(I[i].dis <= 2 * k && i <= nr){//在当前k下能连通的全部连通
Union(I[i].u,I[i].v);
i++;
}
for(int i = 0;i < num;i++){//两两枚举
for(int j = 0;j < num;j++){
if(findfather(i) == findfather(j) && N[i].x - k < 0 && N[j].x + k > len){
return 1;
}
}
}
return 0;
}
double search(double l,double r){
while(l + 0.001 < r){//二分,精度为0.001
double mid = (l + r)/2;
if(check(mid)){//成立就缩小上界
r = mid;
}
else{
l = mid;
}
}
return r;
}
int main(){
cin>>len>>num;//输入宽度和点数
for(int i = 0;i < num;i++)father[i] = i;//并查集初始化
for(int i = 0;i < num;i++){
cin>>N[i].x>>N[i].y;//输入点坐标
}
for(int i = 0;i < num;i++){//两两枚举,计算点与点之间的距离
for(int j = i;j < num;j++){
I[++nr].u = i;
I[nr].v = j;
I[nr].dis =sqrt (1.0 * (N[i].x - N[j].x) * (N[i].x - N[j].x) + (N[i].y - N[j].y) * (N[i].y - N[j].y));//记得加math头文件
}
}
sort(I + 1,I + nr + 1,cmp);//按距离从小到大排序
printf("%.2f\n",search(0.0,10000000.0) );//输出
return 0;
}
End.(求过)