套圈·分治

题目信息

Have you ever played quoit in a playground? Quoit is a game in which flat rings are pitched at some toys, with all the toys encircled awarded.
In the field of Cyberground, the position of each toy is fixed, and the ring is carefully designed so it can only encircle one toy at a time. On the other hand, to make the game look more attractive, the ring is designed to have the largest radius. Given a configuration of the field, you are supposed to find the radius of such a ring.
Assume that all the toys are points on a plane. A point is encircled by the ring if the distance between the point and the center of the ring is strictly less than the radius of the ring. If two toys are placed at the same point, the radius of the ring is considered to be 0.
你曾经在操场上玩过曲棍球吗?Quoit是一种游戏,在游戏中,平环投出一些玩具,与所有的玩具包围授予。
在赛博地面领域,每个玩具的位置都是固定的,圆环经过精心设计,一次只能环绕一个玩具。另一方面,为了让游戏看起来更有吸引力,戒指的设计半径最大。给定一个场的配置,你应该找到这样一个环的半径。
假设所有的玩具都是平面上的点。如果点与环中心之间的距离严格小于环的半径,则点被环包围。如果两个玩具放在同一点上,圆环的半径被认为是0。

输入

The input consists of several test cases. For each case, the first line contains an integer N (2 <= N <= 100,000), the total number of toys in the field. Then N lines follow, each contains a pair of (x, y) which are the coordinates of a toy. The input is terminated by N = 0.
Output For each test case, print in one line the radius of the ring required by the Cyberground manager, accurate up to 2 decimal places.
输入由几个测试用例组成。对于每种情况,第一行包含一个整数 N(2<=N<=100000),即字段中玩具的总数。接下来是N行,每行包含一对(x,y),这是一个玩具的坐标。输入以N=0终止。
输出每个测试用例,在一行中打印Cyberground manager要求的环半径,精确到小数点后2位。

测试样例

4
0 3
3 2
4 0
7 1
0
1.12

解答

#include <iostream>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <algorithm>

using namespace std;

struct Point
{
    double x;
    double y;
} points[10], temp[10];

struct x_sort
{
    bool operator()(const Point &a, const Point &b) const
    {
        return a.x < b.x;
    }
};

struct y_sort
{
    bool operator()(const Point &a, const Point &b) const
    {
        return a.y < b.y;
    }
};

double Dis(Point a, Point b)
{
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

double MinDistance(int start, int end)
{
    //先是寻找递归终点
    if (start + 1 == end)
    {//两个点
        return Dis(points[start], points[end]);
    }
    if (start + 2 == end)
    {//三个点
        double d1 = Dis(points[start], points[start + 1]);
        double d2 = Dis(points[start + 1], points[end]);
        double d3 = Dis(points[start], points[end]);
        return min(d1, min(d2, d3));//返回三个点之间最小的距离
    }

    //分
    int mid_index = (start + end) / 2;//中位数的下标
    double d1 = MinDistance(start, mid_index);//左边点集内的最小距离
    double d2 = MinDistance(mid_index+1, end);//右边点集内的最小距离
    double d = min(d1, d2);
    //比较小的有可能会出现在跨分界的区域内

    //治
    int cnt = 0;//用来记录temp点集中点的个数
    for (int i = start; i <= end; i++)
    { //把x坐标在中界限[-d,d]附近的点收集到temp点集
        if (fabs(points[mid_index].x - points[i].x) <= d)
        {
            temp[cnt++] = points[i];
        }
    }
    sort(temp, temp + cnt, y_sort());//将temp点集按照y坐标排序
    for (int i = 0; i < cnt; i++)
    {//直接枚举,找出收集的点集里的最短距离
        for (int j = i + 1; j < cnt; j++)
        {
            if (temp[j].y - temp[i].y >= d)
            {//没有必要再找了,只会越来越大
                break;
            }
            d = min(d, Dis(temp[i], temp[j]));//更新最小值
        }
    }
    return d;
}

int main()
{
    freopen("E://test.txt", "r", stdin);
    ios::sync_with_stdio(false);
    int N;
    while (true)
    {
        cin >> N;
        if (N == 0)
        {
            break;
        }
        for (int i = 0; i < N; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        sort(points, points + N, x_sort());

        double minDistance = MinDistance(0, N - 1) / 2;
        cout << fixed << setprecision(2) << minDistance << endl;
    }
    return 0;
}

想法

本题最简单的含义便是给出众多的点集,要找到距离最短的两个点
1、 分
对于最初输入的 n 个点构成的点集S,大致均分成两部分:以所有点的x坐标的中位数mid为界,分为点集S1:x坐标比mid小的点 和 点集S2:x坐标比mid大的点。如下图所示:
在这里插入图片描述
很明显,这里要继续分别向点集S1和S2递归地调用求解。递归的终点:要处理的点集S只有两个点或者三个点,直接计算出最小距离返回。
2、治
对于点集S1已经求得最短距离d1,点集S2已经求得最短距离d2,两者的并集S的最短距离d是多少呢?暂且取 d = min(d1, d2)。分离出temp点集
需要注意到,在分界线两侧,容易出现比 d 更小的答案。我们需要在距离分界线 d 之内枚举各点,是否出现比 d 更小的答案,如果有,则要更新d。于是我们将在距离分界线 d 之内的各点储存在temp点集中,方便接下来的讨论。
在这里插入图片描述
在temp点集中找更小值
1.尽管temp点集已经缩小了范围,一一枚举还是有点浪费时间。那么还有更好的优化,我们将 temp点集 按照 y 坐标排序。
2.然后对两两坐标一一枚举求距离。先确定某一点,然后一一枚举其后的其他点,求两点距离:先判断两点的 y 坐标是否超出d,如果超出,则不必再枚举,因为距离必将大于 d,其没有枚举完的点也是。(排序的作用就体现在此,方便枚举与舍弃)
下面这张图可以加深对枚举与舍弃的理解:
在这里插入图片描述
考虑将所有点分别存入两个点集(结构体数组)X、Y内,第一个点集按照x坐标排序好,第二个点集按照y坐标排序好,并且Y点集中的每个点(用结构体/class实现)除了x、y坐标还要储存有对应该点在X点集的下标。一直在X点集内进行分治处理。
注意:在每个递归层中,X、Y、Temp 在 [l, r]内元素是一样的,就是排列方式不同(点集Y与点集Temp需要分离(递归前)和归并(递归后)操作)。然后在选出 “temp点集” 时能直接从Y点集中选取,并存入Temp点集中。因为传入的Y本身有序,就省去了对Y点集排序的时间。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhj12399

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值