R的农场(平面最近点对)

本题实质上要求平面最近点对,推荐几篇博客补充

还有蒟蒻的平面最近点对的学习笔记

有一片农场,R购买了N个守卫,分别让他们站在一定的位置上(守卫不可移动,同一位置上至多有一个守卫).但是守卫们彼此十分厌恶.经研究,当某两个守卫距离≤K,他们就会发生争吵;而想要守卫们和解也不难,只需要R给出的平均工资能使两人满意,他们就会同意和解并成为朋友;当然,如果两个守卫有共同的朋友,他们也会和解成为朋友.R非常不想守卫们争吵,因此他想找出,在能使所有守卫闭嘴的前提下,平均工资的最小值是多少?

题意:平面上有N个点,某些点之间有无向边相连.求出最小的w,使得选择了所有权值≤w的边之后,D=min{dis(u, v)⁡|⁡u,v不连通}≥K.

分析:首先,我们可以将m对有厌恶关系的守卫,看作它们之间有一条权值为w的边,用并查集维护点与点之间的连通性.不难发现,答案具有可二分性,因为D(w)一定会是一个关于平均工资w的单增函数,于是我们可以对每条边按照w从小到大排序,然后二分w,分治法求最近点对,判断w的合法性.

之所以没有详细讲这道题,主要是因为本题就是在求最近点对的模板上加了一个用并查集维护点与点的连通性.

#include<bits/stdc++.h>
using namespace std;
int n,m,fa[100005];
double k;
struct point{
    double x,y;int id;
}a[100005],b[100005];
struct edge{
    int x,y;double w;
}e[200005];
bool cmp1(edge x,edge y){return x.w<y.w;}
bool cmp2(point x,point y){return x.x<y.x;}
bool cmp3(point x,point y){return x.y<y.y;}
int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}//并查集的路径压缩查询操作
double dis(point x,point y){
    return max(fabs(x.x-y.x),fabs(x.y-y.y));
}//求两个点之间的(切比雪夫)距离的函数
double work(int l,int r){
    if(l==r)return 1e18;
//递归边界:该区域内只有一个点,距离视为无穷大
    int mid=(l+r)>>1,i=l,j=mid+1,k=i;
    double midline=(a[mid].x+a[mid+1].x)/2;
//midline即垂直分割线
    double now=min(work(l,mid),work(mid+1,r));
    double d=now;
//归并排序:按照y坐标排序
    while(i<=mid&&j<=r){
		if(cmp3(a[i],a[j]))b[k]=a[i],i++,k++;
		else b[k]=a[j],j++,k++;
    }
    if(i<=mid)while(i<=mid)b[k]=a[i],i++,k++;
    else while(j<=r)b[k]=a[j],j++,k++;
    for(int i=l;i<=r;i++)a[i]=b[i];
    int L=1,R=0;
    for(int i=l;i<=r;i++)
		if(fabs(a[i].x-midline)<=d){
	    	while(L<=R&&(a[i].y-b[L].y)>=d)
            	L++;
	    	for(int j=L;j<=R;j++){
				int x=find(a[i].id);
            	int y=find(b[j].id);
				if(x!=y)
        			now=min(now,dis(a[i],b[j]));
	    	}
	    	b[++R]=a[i];
		}
    return now;
}
bool check(int mid){
    for(int i=1;i<=n;i++)fa[i]=i;//并查集
//因为我们的w二分到了第mid对点所要求的,
//所以第1-mid对点都能被满足要求,和解成为朋友
    for(int i=1;i<=mid;i++){
		int x=find(e[i].x),y=find(e[i].y);
		if(x!=y)fa[y]=x;
    }
    sort(a+1,a+n+1,cmp2);
//按照点的横坐标位置从小到大排序
    return work(1,n)>k;
//函数work返回的是当前w下,最近的两个点间距
}
int main(){
    scanf("%d%d%lf",&n,&m,&k);
//n个点,m对点之间有无向边相连,点之间最小距离要求是k
    for(int i=1;i<=n;i++){
		scanf("%lf%lf",&a[i].x,&a[i].y);
		a[i].id=i;
    }//读入每个点的横纵坐标
    for(int i=1;i<=m;i++)
		scanf("%d%d%lf",&e[i].x,&e[i].y,&e[i].w);
//读入有边相连的一对点:编号x,y及边权w
    sort(e+1,e+m+1,cmp1);
//按照边权从小到大排序,方便二分
    int l=0,r=m,mid,ans;//注意l的初始值
    while(l<=r){
		mid=(l+r)>>1;
		if(check(mid))ans=mid,r=mid-1;
		else l=mid+1;
    }
    printf("%.3lf\n",e[ans].w);
    return 0;
}

posted on 2019-02-11 17:36  PPXppx  阅读(120)  评论(0编辑  收藏  举报