平面最近点对
平面最近点对算是一个经典的问题了,虽然谈不上是什么专门的算法,但是拿出问题模型好好分析一个是有必要的。
给定\(n\)个二元组\((x,y)\),代表同一平面内的\(n\)个点的坐标,求\(\min\{dis_{(p,q)}\}\)。
其中,定义\(dis_{(p,q)}\)代表两点的直线距离,即\(dis_{(p,q)}=\sqrt{(p_x-q_x)^2+(p_y-q_y)^2}\)。
\(Solution\ 1:\)
暴力求解,\(O(n^2)\)枚举两点,直接计算更新答案。
\(Solution\ 2:\)
分治,先将所有点以横坐标为第一关键字,纵坐标为第二关键字排序。
定义一个问题\(divide(l,r)\)代表排序后点\(l\)到点\(r\)这一段的最优解。然后,对于每一个问题,我们取一个中间点\(mid=(l+r)/2\),那么这个问题的解可以分三种情况讨论:
\(1.\)最优的点对在点\(mid\)左侧(包括\(mid\))
\(2.\)最优的点对在点\(mid\)右侧
\(3.\)最优的点对一个在\(mid\)左侧(包括\(mid\)),一个在\(mid\)右侧
显然,对于\(1.2.\)两种情况,我们可以直接通过递归调用子问题\(divide(l,mid)\)和\(divide(mid+1,r)\)来求解,那么我们最大的问题就是解决第三种情况。
试想,如果我们暴力枚举两边的点,那么时间复杂度仍为\(O(n^2)\),分治就失去了意义。考虑一个优化,假设我们已经得到子问题的答案\(d=\min\{divide(l,mid),divide(mid+1,r)\}\),那么任何一个横坐标距\(mid\)大于\(d\)的点都不可能成为更优的解。
那么我们开一个临时数组,将所有横坐标距\(mid\)小于等于\(d\)的点加入这个数组中,然后到这个数字中枚举找到最优解。
考虑再优化一下,设上一步操作得到的点集为\(S\),那么我们还可以将\(S\)按纵坐标排序,如果更新最优解时发现两点纵坐标之差大于\(d\),可以直接退出循环,达到优化的效果。
事实上,可以证明,只枚举横纵坐标与\(mid\)之差小于\(d\)的点,这样的点至多只有\(6\)个,对时间复杂度的贡献是常数级别的,那么分治的时间复杂度就是递归的时间复杂度加上每次扫描加入备选答案点集的时间复杂度,很容易计算得出时间复杂度为\(O(nlog_2n)\)。
关于上文提到点集大小小于等于\(6\)的详细证明,可以看这个博客。
Raid(POJ3714)
Description
在与联盟的战斗中连续失败后,帝国撤退到最后一个据点。 根据其强大的防御系统,帝国击退了联盟攻击的六波浪潮。 经过几个不眠之夜,联盟将军亚瑟注意到,防御系统唯一的弱点就是能源供应。 该系统由N个核电站充电,其中任何一个都会使系统失效。
这位将军很快就开始对N个特工人员进行突袭,这些特工人员进入了据点。 不幸的是,由于帝国空军的袭击,他们未能降落在预期位置。 作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排计划。 他现在想知道的第一件事就是哪个代理商离任何一个发电站最近。 你是否可以帮助将军计算代理人和车站之间的最小距离?
Input Format
第一行是表示测试用例数的整数T. 每个测试用例以整数N开头。 (1 ≤≤ N ≤≤ 100000). 接下来的N行描述了站点的位置。 每行包括两个整数X(0 ≤≤ X ≤≤ 1000000000)和Y(0 ≤≤ Y ≤≤ 1000000000),表示该站的位置。 接下来的N行描述了代理的位置。 每行包含两个整数X(0 ≤≤ X ≤≤ 1000000000)和Y(0 ≤≤ Y ≤≤ 1000000000),表示代理的位置。
Output Format
对于每个测试用例输出,最小距离精度为三位小数放在一个单独的行中。
Sample Input
2
4
0 0
0 1
1 0
1 1
2 2
2 3
3 2
3 3
4
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
Sample Output
1.414
0.000
解析
这道就是平面最近点对模板题嘛。给定\(2n\)个点,求第一个点集中的点到第二个点集中的点的最短距离。对于不同的点集的点,我们直接定义距离为正无穷,然后分治就可以了。
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
#define mset(name,val) memset(name,val,sizeof name)
#define filein(str) freopen(str".in","r",stdin)
#define fileout(str) freopen(str".out","w",stdout)
const int N=100000+20;
const double INF=1e30,eps=1e-5;
int n;
struct node{double x,y;bool flag;}vertex[2*N];
inline void input(void)
{
scanf("%d",&n);
for(int i=1;i<=2*n;i++)
{
scanf("%lf%lf",&vertex[i].x,&vertex[i].y);
if(i<=n)vertex[i].flag=0;
else vertex[i].flag=1;
}
}
inline double dis(node a,node b)
{
if(a.flag==b.flag)return INF;
return sqrt( ( (a.x-b.x) * (a.x-b.x) * 1.0 ) * 1.0 + ( (a.y-b.y) * (a.y-b.y) * 1.0 ) * 1.0 );
}
inline bool comparex(node a,node b)
{
return a.x<b.x;
}
inline bool comparey(node a,node b)
{
return a.y<b.y;
}
inline double divide(int l,int r)
{
if(l==r)return INF;
if(r-l==1)return dis(vertex[l],vertex[r]);
int mid=(l+r)/2;
vector < node > t;t.clear();
double res=min(divide(l,mid),divide(mid+1,r));
for(int i=l;i<=r;i++)
if( fabs( vertex[i].x - vertex[mid].x ) <= res )
t.push_back(vertex[i]);
sort(t.begin(),t.end(),comparey);
for(int i=0;i<t.size()-1;i++)
{
for(int j=i+1;j<t.size();j++)
{
if(t[j].y-t[i].y>res)break;
res=min(res,dis(t[i],t[j]));
}
}
return res;
}
int main(void)
{
int T;
scanf("%d",&T);
while(T--)
{
input();
sort(vertex+1,vertex+2*n+1,comparex);
printf("%.3lf\n",divide(1,2*n));
}
return 0;
}