← 全部文章

CF607E Cross Sum 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF607E Cross Sum

题意

  • 在平面直角坐标系中,给定 nn 条不同的直线,第 ii 条直线为 y=aix+biy=a_i x+b_i。
  • 可重点集 I\mathcal I 为 nn 条直线两两的交点,可重数集 D\mathcal D 为 I\mathcal I 中所有点与 (p,q)(p,q) 的距离。
  • 你需要求出 D\mathcal D 中前 mm 小的数之和。
  • n≤5×104n \le 5 \times 10^4,m≤3×107m \le 3 \times 10^7,∣p∣,∣q∣,∣ai∣,∣bi∣≤103|p|,|q|,|a_i|,|b_i| \le 10^3 且输入的整数值为实际值的 10310^3 倍,答案精度误差 ≤10−6\le 10^{-6}。

题解

为了方便,我们可以直接将 (p,q)(p,q) 作为坐标系原点,则每条直线可以转成 y+q=ai(x+p)+biy+q=a_i(x+p)+b_i,也就是 y=aix+aip+bi−qy=a_ix + a_ip+b_i-q。

首先显然要找到一个半径最小的圆使其能够覆盖至少 mm 个交点。

考虑二分答案转化为判定问题。我们可以求出每条直线与圆的交点坐标并极角排序和离散化,问题就变成若干条线段有多少两两相交,是经典的扫描线问题,可以用树状数组求出。这部分时间复杂度 O(nlog⁡nlog⁡w)\mathcal O(n \log n \log w)。

找到这个半径之后,我们现在要计算答案。

与上面的过程类似,但由于此时交点数最多只有 mm,因此我们可以用 set 代替树状数组,直接得到所有相交的线段,然后求出它们的交点与原点的距离。

但可能有多个点恰好在圆上,因此我们在统计距离的时候只统计严格在圆内的点,如果个数少于 mm,则剩下的距离全都是半径。这部分时间复杂度 O(mlog⁡n)\mathcal O(m \log n)。

总时间复杂度 O(nlog⁡nlog⁡w+mlog⁡n)\mathcal O(n \log n \log w + m \log n),需要注意一下精度问题。

代码

const int N = 5e4 + 7;
int n, m, x, y, t, v[N], c[N*2];
ld p, q, a[N], b[N];
pair<ld, int> s[N*2];

inline void add(int x) {
	while (x <= t) ++c[x], x += x & -x;
}

inline int ask(int x) {
	int k = 0;
	while (x) k += c[x], x -= x & -x;
	return k;
}

inline void work(ld r) {
	t = 0;
	for (int i = 1; i <= n; i++) {
		ld a = ::a[i], b = ::b[i];
		ld d = 4 * a * a * b * b - 4 * (a * a + 1) * (b * b - r * r);
		if (d < eps) continue;
		d = sqrt(d);
		ld x1 = (-2 * a * b - d) / (2 * (a * a + 1));
		ld x2 = (-2 * a * b + d) / (2 * (a * a + 1));
		ld y1 = a * x1 + b, y2 = a * x2 + b;
		s[++t] = mp(P(x1, y1).a(), i), s[++t] = mp(P(x2, y2).a(), i);
		v[i] = 0;
	}
	sort(s + 1, s + t + 1);
	for (int i = t; i; i--)
		if (!v[s[i].se]) v[s[i].se] = i;
}

inline bool pd(ld r) {
	work(r);
	for (int i = 1; i <= t; i++) c[i] = 0;
	ll ret = 0;
	for (int i = 1; i <= t; i++)
		if (v[s[i].se] != i) {
			ret += ask(v[s[i].se]) - ask(i - 1);
			add(v[s[i].se]);
		}
	return ret >= m;
}

inline ld calc(int i, int j) {
	ld x = (b[j] - b[i]) / (a[i] - a[j]), y = a[i] * x + b[i];
	return sqrt(x * x + y * y);
}

inline ld calc(ld r) {
	work(r);
	set<pi> st;
	ld ret = 0;
	int cnt = 0;
	for (int i = 1; i <= t; i++)
		if (v[s[i].se] != i) {
			auto it = st.upper_bound(mp(i + 1, 0));
			while (it != st.end() && (it -> fi) < v[s[i].se])
				ret += calc(s[i].se, it -> se), ++it, ++cnt;
			st.insert(mp(v[s[i].se], s[i].se));
		}
	return ret + (m - cnt) * r;
}

int main() {
	rd(n), rd(x), rd(y), rd(m), p = x / 1e3, q = y / 1e3;
	for (int i = 1; i <= n; i++)
		rd(x), rd(y), a[i] = x / 1e3, b[i] = y / 1e3 + a[i] * p - q;
	ld l = 0, r = 1e10;
	for (int i = 1; i <= 100; i++) {
		ld mid = (l + r) / 2;
		if (pd(mid)) r = mid;
		else l = mid;
	}
	printf("%.10Lf\n", max(0.0L, calc(r)));
	return 0;
}

标签:二分、扫描线、树状数组、计算几何

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…