一、题意

给一个x轴的一段长度[0, L]代表一条公路,然后给出n个点的坐标(xi, yi),要你在公路上选出尽量少的点,使得对于给定的每个点,都有一个选出的点离它的欧几里得距离不超过D。

二、解析

以每个给定的点为圆心,D为半径可以画出n个⚪,这些⚪和公路会相交并得到对应的割线。这些割线就是本题中的区间。问题就变成了,在这n个区间中,选出尽量少的点,使得每个区间都至少包含一个点。
这就是典型的区间选点问题了。

根据贪心的思想,我们将区间按照右端点从小到大排列,由于如果一个大区间完全包含一个小区间,则点显然是要选在小区间里,因此像“二”型这样的两个区间,选点选在右端点小的那个区间中准没错;剩下需要考虑的情况就是左右端点都同时递增的区间关系,这种情况显然也是选点选在右端点小的那个区间中准没错,因为这样这个点就能涵盖这两个区间了。

因此我们的贪心策略是:按右端点排序后,总是选取第一个不被包含的区间的最右端点处。

三、代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#define mp make_pair
using namespace std;
using pdd = pair<double, double>;
int L, D, n;
vector<pdd> vec;  // 存放割线区间

int main() {
    while(cin >> L >> D >> n) {
        vec.clear();
        for(int i = 0, x, y; i < n; i ++) {
            cin >> x >> y;
            double a = sqrt(D * D - y * y), l = x - a, r = x + a;
            l = min((double)L, max((double)0, l)), r = min((double)L, max((double)0, r));
            vec.push_back(mp(l, r));
        }
        sort(vec.begin(), vec.end(), [](const pdd& a, const pdd& b) {
            return a.second < b.second;
        });
        int ans = 0;
        double cur = -1;
        for(const auto& p : vec) if(cur < p.first || cur > p.second) ans ++, cur = p.second;
        cout << ans << endl;
    }
}

四、归纳

  • 区间选点问题:按右端点排列,总是选取第一个还没有被涵盖的区间的右端点。