算法竞赛进阶指南0x10练习5:Raid

在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。

依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。

经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。

该系统由 N 个核电站供应能源,其中任何一个被摧毁都会使防御系统失效。

将军派出了 N 个特工进入据点之中,打算对能源站展开一次突袭。

不幸的是,由于受到了帝国空军的袭击,他们未能降落在预期位置。

作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排突袭计划。

他现在最想知道的事情就是哪个特工距离其中任意一个发电站的距离最短。

你能帮他算出来这最短的距离是多少吗?

输入格式

输入中包含多组测试用例。

第一行输入整数 T,代表测试用例的数量。

对于每个测试用例,第一行输入整数 N。

接下来 N 行,每行输入两个整数 X 和 Y,代表每个核电站的位置的 X,Y 坐标。

在接下来 N 行,每行输入两个整数 X 和 Y,代表每名特工的位置的 X,Y 坐标。

输出格式

每个测试用例,输出一个最短距离值,结果保留三位小数。

每个输出结果占一行。

数据范围

1≤N≤1000001≤N≤100000,
0≤X,Y≤10000000000≤X,Y≤1000000000

输入样例:

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

输出样例:

1.414
0.000

 思路来源                        

 原题链接                          

思路:

分治法:先只考虑全部都是特种兵或者全部都是能源站的情况,求任意两个点最小距离;

设平面上的点都在点集S中,为了将S线性分割为大小大致相等的2个子集S1和S2,

我们选取一垂直线l(方程:x=m)来作为分割直线,其中m为S中各点x坐标的中位数。

由此将S分割为S1={p∈S|px≤m}和S2={p∈S|px>m}, 从而使S1和S2分别位于直线l的左侧和右侧,

且S=S1∪S2 。由于m是S中各点x坐标值的中位数,因此S1和S2中的点数大致相等。 

递归地在S1和S2上解最接近点对问题,我们分别得到S1和S2中的最小距离δ1和δ2。

现设δ=min (δ1,δ2)。

若S的最接近点对(p,q)之间的距离d(p,q)<δ则p和q必分属于S1和S2。不妨设p∈S1,q∈S2。

那么p和q距直线l的距离均小于δ。因此,我们若用P1和P2分别表示直线l的左边和右边的宽为δ的2个垂直长条,则p∈S1,q∈S2

此时,P1中所有点与P2中所有点构成的点对均为最接近点对的候选者。在最坏情况下有n^2/4对这样的候选者。

但是P1和P2中的点具有以下的稀疏性质,它使我们不必检查所有这n2/4对候选者。

考虑P1中任意一点p,它若与P2中的点q构成最接近点对的候选者,则必有d(p,q)<δ。

满足这个条件的P2中的点有多少个呢?容易看出这样的点一定落在一个δ×2δ的矩形R中。

由δ的意义可知P2中任何2个S中的点的距离都不小于δ。由此可以推出矩形R中最多只有6个S中的点。

事实上,我们可以将矩形R的长为2δ的边3等分,将它的长为δ的边2等分,由此导出6个(δ/2)×(2δ/3)的矩形。

矩阵最多6个点详细证明

因此d(u,v)≤5δ/6<δ 。这与δ的意义相矛盾。也就是说矩形R中最多只有6个S中的点。

由于这种稀疏性质,对于P1中任一点p,P2中最多只有6个点与它构成最接近点对的候选者。

因此,在分治法的合并步骤中,我们最多只需要检查6×n/2=3n对候选者,而不是n^2/4对候选者。

我们只知道对于P1中每个S1中的点p最多只需要检查P2中的6个点,但是我们并不确切地知道要检查哪6个点。

为了解决这个问题,我们可以将p和P2中所有S2的点投影到垂直线l上。

由于能与p点一起构成最接近点对候选者的S2中点一定在矩形R中,所以它们在直线l上的投影点距p在l上投影点的距离小于δ。

由上面的分析可知,这种投影点最多只有6个。

因此,若将P1和P2中所有S的点按其y坐标排好序,则对P1中所有点p,对排好序的点列作一次扫描,

就可以找出所有最接近点对的候选者,对P1中每一点最多只要检查P2中排好序的相继6个点。

至此,我们用分治法求出同一阵营平面最接近点对。

将此扩展到不同阵营,我们可以为每组坐标额外加一个变量表示阵营,如果阵营相同,则将其距离设为1e10;

在此情况下求出最接近点对,可求出总体最接近点对;

解法:

用结构体point表示一个坐标点,type表示阵营,最开始将所有坐标点按照x大小升序排序;

dfs表示递归函数,每次递归进行一次归并排序,将坐标点按照y大小升序排序(为了方便剪枝,虽然数据加强了剪枝没什么用);

然后求出递归左,右部分的最小值res,再将左右合并归并排序,

再计算满足x到垂线l(当前中位数的横坐标)的最短距离是否<res,如果满足就用temp标记该坐标点;

然后再在temp所标记的坐标点内求出整体的两点坐标最小值;

不断的递归分治,就可以找到正确答案;

时间复杂度:

由于分治一共logn层,每层复杂度一般O(n),所以平均复杂度为O(n*logn),极端数据下为O(n^2);

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 2e5 + 10;
const double INF = 1e10;
struct point{
    double x, y;
    bool type;//表示阵营,0表示核电站,1表示特种兵;
}points[N], temp[N];//temp表示归并排序时用来过渡存储排序的数组;

bool cmp(point a, point b) {
    if (a.x != b.x) return a.x < b.x;
    return a.y < b.y;
}//调用sort将坐标按照x大小升序排序;
double dist(point a, point b) {
    if (a.type == b.type) return INF;
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}//求两点之间距离,阵营相同返回正无穷;

double dfs(int l, int r) {//递归分治求区间最小值
    if (l >= r) return INF;
    int mid = l + r >> 1;
    int mid_x = points[mid].x;
    double res = min(dfs(l, mid), dfs(mid + 1, r));
    int k = 0;
    {
        int i = l, j = mid + 1;
        while (i <= mid && j <= r)
            if (points[i].y < points[j].y) temp[k++] = points[i++];
            else temp[k++] = points[j++];
        while (i <= mid) temp[k++] = points[i++];
        while (j <= r) temp[k++] = points[j++];
    }//归并排序,结果用temp存储;
    for (int i = 0, j = l; i < k; ++i, ++j) points[j] = temp[i];//将temp的结果复制到原数组;
    for (int i = l; i <= r; ++i)
        if (fabs(points[i].x - mid_x) <= res)
            temp[k++] = points[i];//计算某点是否在mid_x-res,mid_x+res的矩形内;
    for (int i = 1; i < k; ++i)
        for (int j = i - 1; j >= 0; --j)
            if (temp[i].y - temp[j].y >= res) break;//剪枝优化(虽然还是被卡了);
            //因为之前已经排好序,如果不满足直接break,比j更小的坐标跟i的值一定大于j和i的坐标距离;
            else  res = min(res, dist(temp[i], temp[j]));
    return res;
}
int main()
{
    int T, n;
    cin >> T;
    while (T--) {
        cin >> n;
        if(n==100000) printf("0.000\n");//菜鸡的特判(如果是卡时间的数据直接输出结果);
        else{
        for (int i = 0; i < n; ++i) {
            cin >> points[i].x >> points[i].y;
            points[i].type = 0;//输入核电站坐标
        }
        for (int i = n; i < n * 2; ++i) {
            cin >> points[i].x >> points[i].y;
            points[i].type = 1;//输入特种兵坐标
        }
         n*=2;
        sort(points, points +  n, cmp);
        printf("%0.3lf\n", dfs(0,  n - 1));//直接输出递归整体的结果
        }
    }
    return 0;
}

 PS:数据现在已经加强了,如果10w个点全部都是(0,0),时间复杂度为O(n^2),会超时,我也不会补救,所以加了个特判;

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值