其他分享
首页 > 其他分享> > 「APIO2018」「LOJ #2586」选圆圈

「APIO2018」「LOJ #2586」选圆圈

作者:互联网

Description

给定平面上的 \(n\) 个圆,用三个参数 \((x, y, R)\) 表示圆心坐标和半径。

每次选取最大的一个尚未被删除的圆删除,并同时删除所有与其相切或相交的圆。

最后输出每个圆分别是被那个圆所删除的。

Hint

Solution 1

有一个非常简单的 \(O(n^2)\) 暴力,由于每次都要扫一遍所有圆所以复杂度爆炸。

我们尝试剪枝,缩小枚举的范围。

若当前最大圆的半径为 \(R\),那么我们做一个分块操作:将整个平面 划分为一个个方格,方格的边长为 \(2R\),刚好“框住”这个最大圆。

对于当前这个圆,我们只要搜索 其所在格子及其相邻的 即可(两个格子相邻定义为所在行的距离不超过 1,且列距离也不超过 1,换言之,一个格子所有相邻的就是周围一圈 8 个)。显然这样是不会漏记的,因为所有圆的半径都不超过方格大小,那么一定不会出现两个圆相交或相切,但却不在相邻两个方格内。

但我们总不能每次都搜怎么大的格子,最大圆的大小如果非常大而其他圆又很小的话这个剪枝没有丝毫用处。因此我们引入一个 重构机制:设现在考虑到的圆的半径为 \(R^\prime\),原来方格大小为 \(L\)。若 \(R^\prime < L\),那么 重构整个方格,并以 \(2R^\prime\) 作为新的方格大小 \(L\)。

重构的复杂度会不会有问题?观察到,当半径不足方格大小的一半时才会重构,重构之后如果又来一次那又得一半。于是整个算法不会有超过 \(O(\log R)\) 次重构,而一次重构的复杂度可以达到 \(O(n\log n)\)(排序时重构,搜索时二分),所以总共最多也就两只 \(\log\),不会有问题。

可以证明每个圆被检查的次数为常数。但是我不会,这里就不贴了,如果可以提供证明请私信,谢谢!

总复杂度为 \(O(n\log n\log R)\)。听说哈希表可以搞成一个 \(\log\)。

Code for Solution 1

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : APIO2018 LOJ #2586 选圆圈
 */
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;
const int N = 3e5 + 5;
const int inf = 1e9;

int n;
struct Triad {
    int x, y, r;
    inline void read() {
        cin >> x >> y >> r;
        x += inf, y += inf;
    }
} circ[N];
int ans[N];

int cir_ord[N];
vector<Triad> f;
vector<int> g[N];
int bsiz;

inline long long sqr(int x) {
    return x * 1ll * x;
}
inline bool connect(Triad& a, Triad& b) {
    return sqr(a.x - b.x) + sqr(a.y - b.y) <= sqr(a.r + b.r);
}

typedef vector<Triad>::iterator vecIt;
inline void init_blocks(int L) {
    if (bsiz && bsiz / 2 < L) return;
    bsiz = L; f.clear();
    for (int i = 1; i <= n; i++) if (!ans[i])
        f.push_back(Triad{circ[i].x / bsiz, circ[i].y / bsiz, i});
    sort(f.begin(), f.end(), [](Triad& a, Triad& b) {
        return a.x != b.x ? a.x < b.x : a.y < b.y;
    });
    int cnt = 0;
    for (vecIt i = f.begin(), j; i != f.end(); i = j, ++cnt) {
        for (j = i; j != f.end(); j++)
            if (i->x != j->x || i->y != j->y) break;
        f[cnt] = *i, g[cnt].clear();
        for (vecIt k = i; k != j; k++) g[cnt].push_back(k->r);
    }
    f.resize(cnt);
}

void solve(int p) {
    if (ans[p]) return;
    init_blocks(circ[p].r * 2);

    int x = circ[p].x / bsiz;
    int y = circ[p].y / bsiz;
    for (int i = x - 1; i <= x + 1; i++)
        for (int j = y - 1; j <= y + 1; j++) {
            if (i < 0 || j < 0) continue;
            int c = lower_bound(f.begin(), f.end(), Triad{i, j, 0}, [](Triad& a, const Triad& b) {
                return a.x != b.x ? a.x < b.x : a.y < b.y;
            }) - f.begin();
            if (c == int(f.size()) || f[c].x != i || f[c].y != j)
                continue;

            vector<int> buf;
            for (auto k : g[c])
                if (connect(circ[k], circ[p])) ans[k] = p;
                else buf.push_back(k);
            g[c].swap(buf);
        }
}

signed main() {
    ios::sync_with_stdio(false);

    cin >> n;
    for (int i = 1; i <= n; i++)
        circ[i].read(), cir_ord[i] = i;
    sort(cir_ord + 1, cir_ord + 1 + n, [](int& a, int& b) {
        return circ[a].r != circ[b].r ? circ[a].r > circ[b].r : a < b;
    });

    for (int i = 1; i <= n; i++)
        solve(cir_ord[i]);
    for (int i = 1; i <= n; i++)
        cout << ans[i] << ' ';
    return cout << endl, 0;
}

Solution 2

既然是“二维平面”,那么可以 KDT 暴力搞。

首先一个圆心为 \((x, y)\),半径为 \(R\) 的圆,我们可以粗略地认为是一个矩形区域 \((x-R, y-R)\sim (x+R, y+R)\)。

那么考虑用 KDT 维护这些矩形。对于一个圆,搜索可能与之有交集的区域即可。

但这样复杂度是 \(O(\text{玄学})\) 而且可能被卡。

于是发扬人类智慧,将所有的点 随机旋转 一个角度。于是就可以过 LOJ 数据了。

不过Luogu 不需要旋转 qwq。

Code for Solution 2

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : APIO2018 LOJ #2586 选圆圈
 */
#include <algorithm>
#include <cmath>
#include <iostream>

using namespace std;
const double eps = 1e-3;
const int N = 3e5 + 5;
const int K = 2;

int n;
struct circle {
    double p[K]; int r;
};
pair<circle, int> C[N];
int ans[N];

struct area {
    double max[K], min[K];
};

inline area trans(circle a) {
    return area{{a.p[0] + a.r, a.p[1] + a.r}, {a.p[0] - a.r, a.p[1] - a.r}};
}

struct node {
    int lc, rc;
    area range;
    circle val;
    int index, vaild;
} t[N];
int total = 0;

inline void pushup(int x) {
    t[x].range = trans(t[x].val);
    t[x].vaild = t[t[x].lc].vaild + t[t[x].rc].vaild + 1;
    for (int i = 0; i < K; i++) {
        if (t[x].lc) {
            t[x].range.max[i] = max(t[x].range.max[i], t[t[x].lc].range.max[i]);
            t[x].range.min[i] = min(t[x].range.min[i], t[t[x].lc].range.min[i]);
        }
        if (t[x].rc) {
            t[x].range.max[i] = max(t[x].range.max[i], t[t[x].rc].range.max[i]);
            t[x].range.min[i] = min(t[x].range.min[i], t[t[x].rc].range.min[i]);
        }
    }
}

int build(int l, int r, int d) {
    if (l > r) return 0;
    int mid = (l + r) >> 1, x = ++total;
    nth_element(C + l, C + mid, C + r + 1, [&](pair<circle, int> a, pair<circle, int> b) {
        return a.first.p[d] < b.first.p[d];
    });
    t[x].val = C[mid].first;
    t[x].index = C[mid].second;
    t[x].lc = build(l, mid - 1, d ^ 1);
    t[x].rc = build(mid + 1, r, d ^ 1);
    return pushup(x), x;
}

inline bool outside(circle& a, area& b) {
    double x = a.p[0], y = a.p[1]; int r = a.r;
    if (b.min[0] - (x + r) >= eps) return true;
    if (b.min[1] - (y + r) >= eps) return true;
    if ((x - r) - b.max[0] >= eps) return true;
    if ((y - r) - b.max[1] >= eps) return true;
    return false;
}
inline double sqr(double x) {
    return x * x;
}
inline bool connect(circle& a, circle& b) {
    return sqr(a.r + b.r) - (sqr(a.p[0] - b.p[0]) + sqr(a.p[1] - b.p[1])) >= -eps;
}
void select(int x, pair<circle, int>& c) {
    if (!x || outside(c.first, t[x].range)) return;
    if (!ans[t[x].index] && connect(c.first, t[x].val))
        ans[t[x].index] = c.second;
    if (t[t[x].lc].vaild) select(t[x].lc, c);
    if (t[t[x].rc].vaild) select(t[x].rc, c);
    t[x].vaild = t[t[x].lc].vaild + t[t[x].rc].vaild + (!ans[t[x].index] ? 1 : 0);
}

inline void rotate(double theta) {
    double SIN = sin(theta), COS = cos(theta);
    for (int i = 1; i <= n; i++) {
        double x = C[i].first.p[0];
        double y = C[i].first.p[1];
        C[i].first.p[1] = x * SIN + y * COS;
        C[i].first.p[0] = x * COS - y * SIN;
    }
}

signed main() {
    ios::sync_with_stdio(false);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> C[i].first.p[0] >> C[i].first.p[1] >> C[i].first.r;
        C[i].second = i;
    }
    rotate(2.3);

    int root = build(1, n, 0);

    sort(C + 1, C + 1 + n, [](pair<circle, int>& a, pair<circle, int>& b) {
        return a.first.r != b.first.r ? a.first.r > b.first.r : a.second < b.second;
    });
    for (int i = 1; i <= n; i++)
        if (!ans[C[i].second]) select(root, C[i]);
    
    for (int i = 1; i <= n; i++)
        cout << ans[i] << ' ';
    return cout << endl, 0;
}

标签:2586,APIO2018,return,min,LOJ,max,int,range,first
来源: https://www.cnblogs.com/-Wallace-/p/13528625.html