题意
在二维平面上有很多个互不重叠、相交、包含的圆。求出有多少个切点。
思路
考虑用到扫描线,将每个圆的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;
}
坐标系旋转证明:平面内直角坐标系中坐标旋转变换公式