题目大意

在平面直角坐标系中,有 $n$ 个圆。需要找到一个方向,使得从原点按该方向射出的一条射线,每隔距离 $d$ 为一个点,这些点落在圆的个数最大(同一个圆可以贡献多次)。

$n\leq2\times10^4,5\leq d\leq10.$

思路

考虑一个圆被以原点为圆心、半径为 $k\cdot d$ 的若干个圆交。如下图。

Figure 1

发现每一个在圆内的弧线的贡献都是 $1$ ,那么就把弧区间化:把每个弧与圆的两个交点与 $x$ 轴的弧度求出来(即把点转到极角坐标,取其弧度),然后差分即可。

至于弧度的求法,先用反正切求出圆心的弧度 $\alpha$ ,见下图:

Figure 2

由于 $R,l,r$ 已知,显然可以用余弦定理求得 $\cos\theta=\dfrac{R^2+l^2-r^2}{2Rl}$ ,用反余弦就可以求 $\theta$ 了,那么弧度的区间就是 $[\alpha-\theta,\alpha+\theta]$ 注意判断 $2\pi$ 边界情况。

代码

const int N = 2e4 + 10;
const long double pi = acos(-1), eps = 1e-5;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

namespace Main {
	
	int n, d, cnt;
	
	long double rL[N * 10], rR[N * 10];
	
	inline long double Abs(long double a) { return a < 0? -a: a; }
	
	inline void addRange(long double l, long double r) {
		rL[++cnt] = l, rR[cnt] = r;
	}
	
	void Work (int R, int x, int y, int r, long double alpha, long double l) {
		long double theta = acos((R * R + x * x + y * y - r * r) * 1.0 / (2 * R * l));
		if (alpha - theta < eps) {
			addRange(alpha - theta + 2 * pi, 2 * pi - eps);
			addRange(0, alpha + theta);
		} else if (2 * pi - (alpha + theta) < eps) {
			addRange(alpha - theta, 2 * pi);
			addRange(eps, alpha + theta - 2 * pi);
		} else addRange(alpha - theta, alpha + theta);
	}
	
	int main () {
		n = Read(), d = Read();
		for (int i = 1; i <= n; i++) {
			ll x = Read(), y = Read(), r = Read();
			long double l = sqrt(x * x + y * y), alpha = atan2(y, x);
			if (alpha < 0) alpha += 2 * pi;
			ll intL = ceil(l);
			int nearD = intL - intL % d;
			if (Abs(nearD + d - l) < Abs(nearD - l)) nearD += d;
			
			for (int R = nearD; Abs(R - l) <= r; R += d) {
				Work(R, x, y, r, alpha, l);
			}
			for (int R = nearD - d; Abs(R - l) <= r; R -= d) {
				Work(R, x, y, r, alpha, l);
			}
		}
		sort (rL + 1, rL + 1 + cnt);
		sort (rR + 1, rR + 1 + cnt);
		int ans = 0, tmp = 0;
		for (int i = 1, j = 1; i <= cnt && j <= cnt; ) {
			for (; rL[i] <= rR[j] && i <= cnt; i++, tmp++);
			ans = max(ans, tmp);
			for (; rR[j] <= rL[i] && j <= cnt; j++, tmp--);
		}
		printf ("%d\n", ans);
		return 0;
	}
}

int main () {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	Main::main();
	return 0;
}
EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2023-09-17 16:26:59
博客类型
标签