bzoj4642 泡泡 set维护扫描线

1 篇文章 0 订阅
1 篇文章 0 订阅

在这里插入图片描述

题意

在二维平面上有很多个互不重叠、相交、包含的圆。求出有多少个切点。

思路

考虑用到扫描线,将每个圆的x轴坐标作为排序关键,每个圆只在插入和弹出时对答案产生贡献,每个圆的左端点为插入记为type1,右端点为弹出记为type0。当扫描线扫到一个新的event时
当插入一个圆时,这个圆和其他在扫描线上(扫描线当前穿过的圆可能会相切,而且相切一定是离当前插入圆心y坐标最近的两个圆,所以只用枚举2个圆是否和当前圆相切
当弹出一个圆时,其他在扫描线上中在这个圆上方和下方的圆可能会相切,所以只用枚举1对圆
用重载运算符的set维护扫描线是真的方便

struct cmp {bool operator() (const int &x, const int &y) {return cs[x].y < cs[y].y;}};
set<int, cmp> line;

注意:这里讨论的切线都一定不垂于x轴,所以开始时要把坐标系随便旋转一个小角度,否则会TLE,用坐标系旋转公式:x1=xcos(β)-ysin(β); y1=ycos(β)+xsin(β);
总结:用一条线把多个圆都串起来,以降低每个圆的枚举个数,降低复杂度。
其实看代码更容易理解吧

#include <bits/stdc++.h>
#define jh(x, y) (x ^= y, y ^= x, x ^= y)
#define ld long double
#define enter putchar('\n')
#define space putchar(' ')
using namespace std;
template <class T> inline void read(T &x) {
    x = 0; T f = 1; char ch = getchar();
    while (!(ch >= '0' && ch <= '9')) {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}
template <class T> inline void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x / 10) write(x / 10);
    putchar('0' + x % 10);
}
const int N = 5e5 + 10;
const ld eps = 1e-8, _s=sin(0.43415),_c=cos(0.43415);
int n, ans = 0;
struct cir {ld x, y, r;} cs[N];
struct event {
    ld x; int id, type;
    inline bool operator < (const event &b) const {
        return x < b.x;
    }
} es[N << 1];
pair<int, int> as[N << 2];
int ap = 0;
struct cmp {bool operator() (const int &x, const int &y) {return cs[x].y < cs[y].y;}};
set<int, cmp> line;
inline void chk(int a, int b) {
    ld x = cs[a].x - cs[b].x, y = cs[a].y - cs[b].y;
    if (fabs(sqrt(x * x + y * y) - cs[a].r - cs[b].r) < eps) {
        if (a > b) jh(a, b);
        as[++ap] = make_pair(a, b);
    }
}
int main() {
    read(n);
    for (int i = 1; i <= n; i++) {
        int x, y, r; read(x), read(y), read(r);
        cs[i].x = _c * x + _s * y;
        cs[i].y = -_s * x + _c * y;
        cs[i].r = r;
        es[i << 1] = (event){cs[i].x - cs[i].r, i, 1};
        es[i << 1 | 1] = (event){cs[i].x + cs[i].r, i, 0};
    }
    n <<= 1;
    sort(es + 1, es + n + 1);
    for (int i = 1; i <= n; i++) {
        event w = es[i];
        if (w.type) {
            set<int, cmp>::iterator it = line.upper_bound(w.id);
            if (it != line.end()) chk(*it, w.id);
            if (it != line.begin()) --it, chk(*it, w.id);
            line.insert(w.id);
        } else {
            set<int, cmp>::iterator it = line.find(w.id);
            if (it != line.begin()) {
                int a = *--it; ++it; ++it;
                if (it != line.end()) chk(*it, a);
                --it;
            } line.erase(it);
        }
    }
    sort(as + 1, as + ap + 1);
    for (int i = 1; i < ap; i++) if (as[i] != as[i + 1]) ++ans;
    write(ans), enter;
    return 0;
}

坐标系旋转证明:平面内直角坐标系中坐标旋转变换公式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值