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;
}
posted @ 2020-08-06 21:36  JiaZP  阅读(59)  评论(0编辑  收藏  举报