$O(n^2)$ 的 Voronoi Diagram (V 图)简单算法

本来是想 WF 之后再写的,但是 WF 推迟了,也就先写了吧。

我们知道,我们其实是可以用半平面交来求 V 图的,就是每个点和其他所有点中垂线半平面的交,时间复杂度是 \(n\) 次半平面交,\(O(n^2\log_2 n)\)

但是,我们其实可以暴力做半平面交,维护已经考虑的半平面交出来的凸包,然后加入新的半平面,以 \(O(凸包大小)\) 的时间更新。

如果我们在 shuffle 半平面后再干这件事情,其实时间复杂度就是期望 \(O(n^2)\) 了,实际效率比暴力半平面交类似或者更快。

对于最远点的 V 图,我们一样的做法时间复杂度也是 \(O(n^2)\)

附一份代码,【WF2018】 熊猫保护区

std::vector<line> cut(const std::vector<line> & o, line l) {
	std::vector<line> res;
	int n = size(o);
	for(int i = 0;i < n;++i) {
		line a = o[i], b = o[(i + 1) % n], c = o[(i + 2) % n];
		int va = check(a, b, l), vb = check(b, c, l);
		if(va > 0 || vb > 0 || (va == 0 && vb == 0)) {
			res.push_back(b);
		}
		if(va >= 0 && vb < 0) {
			res.push_back(l);
		}
	}
	return res;
}
std::vector<std::vector<line>> voronoi(std::vector<vec2> p) {
	int n = p.size();
	auto b = p; shuffle(b.begin(), b.end(), gen);
	const db V = 3e4;
	std::vector<std::vector<line>> a(n, {
		{V, 0, V * V}, {0, V, V * V},
			{-V, 0, V * V}, {0, -V, V * V},
	});
	for(int i = 0;i < n;++i) {
		for(vec2 x : b) if((x - p[i]).abs() > eps) {
			a[i] = cut(a[i], bisector(p[i], x));
		}
	}
	return a;
}
时间复杂度证明 记 $f(x, S) $ 为,$x$ 与 $S-\{x\}$ 中的点中垂线半平面交的凸包大小。

n 个点的 V 图有 \(O(n)\) 个边界,故 \(\sum_{x\in S} f(x, S) = O(|S|)\)

假设 \(p\) 是所有点的均匀随机排列:

\[\begin{aligned} E[\sum_{i=1}^n \sum_{j=1}^n f(p[i], p[1\cdots j])]&= \sum_{j=1}^n \sum_{i=1}^n E[f(p[i], p[1\cdots j])] \\ &= \sum_{j=1}^n (O(j)+\sum_{i=j+1}^n E[f(p[i], p[1\cdots j])]) \\ &= O(n^2) \end{aligned} \]

最后一步懒得写了,大概你可以认为先 sample 出了 \(\{p[i]\} \cup p[1\cdots j]\) 整个集合,再 sample 出了 \(p[i]\),所以期望是 \(O(1)\) 的。

方差太难了,不会分析。

特别需要注意的是,我们并没有分析出来对于一个 \(x\)\(\sum_{j=1}^n f(x, p[1\cdots j])\) 是期望 \(O(n)\) 的,事实上这也是错的。

posted @ 2023-11-14 13:29  skip2004  阅读(372)  评论(0编辑  收藏  举报