P3268 [JLOI2016]圆的异或并
两两不交?树形关系!
但是暴力建树是 \(O(n^2)\) 的。如何优化建树?
法一:K-D Tree 优化建树
毕竟这是个二维问题,K-D Tree 最擅长搞了。然后我们会发现,这种方法只有70pts。不过也没必要吃惊,毕竟 K-D Tree 的复杂度本来就是 \(O(玄学)\)。
法二:扫描线
我们拿出远古时期(这里指五个月前)学过的算法:扫描线。从左向右扫,动态维护纵轴信息。
由于题目保证圆圆不交,纵轴上的相对关系永远不变(除非扫过了某个圆)。而我们只需要知道新加的那个圆旁边(或者包住它的)那个圆的贡献类型即可。
直接用 set 维护加入的圆,分上半圆(上括号)和下半圆(下括号),不过要重载运算符,需要比较的时候现场算纵坐标。对于每个新加的圆,我们查它的前驱(下驱)是不是上括号,据此判断贡献类型。
值得注意的是,扫描线与圆相切的时候(题目保证圆圆不相切,但是扫描线肯定会与圆相切),可能会出一些奇奇怪怪的错误,因此我们最好在算纵坐标的时候根据类型加减 \(eps\),防止“相等”。
还有,set 有一个非常好用的功能:\(insert\) 函数有一个 pair 类型的返回值,其中第一个是迭代器,第二个是插进去的值。这样我们就不用再 \(lower_bound\) 惹出事端。
关键代码(tbl风格,因为我借鉴他的题解):
struct cir {
int type, x, y, r, cur;
cir(int t = 0, int xx = 0, int yy = 0, int rr = 0, int curr = 0) {
type = t, x = xx, y = yy, r = rr, cur = curr;
}
inline double gety() { return y + type * (sqrt(r * r - (x - nw) * (x - nw)) + eps);}
}ci[N << 1];
bool operator <(cir a, cir b) {
double t1 = a.gety(), t2 = b.gety();
return t1 - t2 < -eps;
}
PII pr[N << 1];
set<cir> st;
int Tp[N];//Attention!!!
signed main() {
read(n);
for (register int i = 1; i <= n; ++i) {
int x, y, r; read(x), read(y), read(r);
ci[i] = cir(1, x, y, r, i);
ci[i + n] = cir(-1, x, y, r, i);
}
for (register int i = 1; i <= n; ++i)
pr[i] = MP(ci[i].x - ci[i].r, i), pr[i + n] = MP(ci[i].x + ci[i].r, i + n);
sort(pr + 1, pr + 1 + n + n);
for (register int i = 1; i <= n + n; ++i) {
int d = pr[i].second;
nw = pr[i].first;//Attention!!!!
if (d <= n) {
set<cir>::iterator it = st.insert(ci[d]).first;
if (it == st.begin()) Tp[d] = 1;
else {
--it;
int t = (*it).type;
if (t == 1) Tp[d] = Tp[(*it).cur];
else Tp[d] = -1 * Tp[(*it).cur];
}
st.insert(ci[d + n]);
} else {
d -= n;
st.erase(ci[d]); st.erase(ci[d + n]);
}
}
int ans = 0;
for (register int i = 1; i <= n; ++i)
ans += Tp[i] * ci[i].r * ci[i].r;
printf("%lld\n", ans);
return 0;
}