2024 牛客寒假训练营(6)— H 题题解。
题目链接
思路
首先很容易就想到一个最暴力的做法:
- 设 g [ i ] [ j ] g[i][j] g[i][j] 表示点 i i i 到点 j j j 这段线段与圆的交点数。
- 我们可以枚举构成三角形的三个点 i 、 j 、 k , ( 1 ≤ i < j < k ≤ n ) i、j、k,(1 \le i < j < k \le n) i、j、k,(1≤i<j<k≤n) ,统计三角形边上的点数之和。
- 则 s u m = ∑ 1 ≤ i < j < k ≤ n g [ i ] [ j ] + g [ i ] [ k ] + g [ j ] [ k ] sum = \sum _{1 \le i < j < k\le n} g[i][j] + g[i][k] + g[j][k] sum=∑1≤i<j<k≤ng[i][j]+g[i][k]+g[j][k]
- sum 是我们的答案吗?
- 并不是
- 如果一个点恰好在圆上,则按照上面的做法,每枚举一个包含这个点的三角形,就会重复计算一次这个点,所以我们要减去重复计算的部分
- a n s = s u m − ∑ 1 ≤ i ≤ n ( n − 1 ) ∗ ( n − 2 ) / 2 ∗ ( d i s t [ i ] = = r ) ans = sum - \sum_{1 \le i \le n} (n-1)*(n-2)/2 * (dist[i] == r) ans=sum−∑1≤i≤n(n−1)∗(n−2)/2∗(dist[i]==r), d i s t [ i ] dist[i] dist[i] 是 i i i 到圆心的距离。
- 复杂度 O ( n 3 ) O(n^3) O(n3)
怎么优化呢?
- 我们考虑每个 g [ i ] [ j ] g[i][j] g[i][j] 在上面第 3 3 3 步中加了多少次,当 i , j i,j i,j 这两个点确定选之后,只要再选一个点即可,包含 g [ i ] [ j ] g[i][j] g[i][j] 的三角形有 n − 2 n-2 n−2 个,所以 g [ i ] [ j ] g[i][j] g[i][j] 被加了 n − 2 n-2 n−2 次。
- 所以 s u m = ∑ 1 ≤ i < j ≤ n g [ i ] [ j ] ∗ ( n − 2 ) sum = \sum_{1\le i < j \le n} g[i][j] * (n-2) sum=∑1≤i<j≤ng[i][j]∗(n−2)。
- 复杂度 O ( n 2 ) O(n^2) O(n2)。
怎么求线段与圆的交点数量?
设线段端点是 p 1 , p 2 p1,p2 p1,p2,到圆心的距离是 d 1 , d 2 d1,d2 d1,d2,圆心到直线的距离为 d d d, p 1 − p 2 直线方程为 l 1 : A x + B y + C = 0 p1-p2直线方程为 l_1:Ax + By + C = 0 p1−p2直线方程为l1:Ax+By+C=0,圆心坐标是 ( a , b ) (a,b) (a,b)。过圆心与 p 1 − p 2 p1-p2 p1−p2 垂直的直线为 l 2 : B x − A y + A b − B a = 0 l_2:Bx - Ay + Ab - Ba = 0 l2:Bx−Ay+Ab−Ba=0
- 如果两个点都在圆的内部,则没有交点。
if (d1 < r&& d2 < r) return 0;
- 若一个点在圆内部,另一个点不在圆内部,则有一个交点。
else if(d1 < r || d2 < r) return 1;
- 若没有点在圆的内部:
- 若 d > r d > r d>r ,则没有交点。
- 若 d = r d = r d=r ,则检查 p 1 、 p 2 p1、p2 p1、p2 是否分布在 l 2 l_2 l2 两边(或者有一个点距离等于 r r r),若是则有 1 1 1 个交点,若不是则没有交点。
- 若 d < r d < r d<r ,则检查 p 1 、 p 2 p1、p2 p1、p2 是否分布在 l 2 l_2 l2 两边,若是则有 2 2 2 个交点,若不是则检查有没有点在圆上,若有则有一个交点,反之没有交点。
Ac_code
import math
N = 1010
def dist(a, b):
return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2
def dist_line(A, B, C, x, y):
up = abs(A * x + B * y + C)
up *= up
down = A * A + B * B
return up, down
def f(A, B, C, x, y):
return A * x + B * y + C
def count(x1, y1, x2, y2, a, b, r):
d1 = dist((a, b), (x1, y1))
d2 = dist((a, b), (x2, y2))
if d1 < r*r and d2 < r*r:
return 0
elif d1 < r*r or d2 < r*r:
return 1
else:
A = y2 - y1
B = x1 - x2
C = y1 * (x2 - x1) + x1 * (y1 - y2)
up, down = dist_line(A, B, C, a, b)
if up > down * r * r:
return 0
nA = B
nB = -A
nC = A * b - B * a
if up == down * r * r:
if f(nA, nB, nC, x1, y1) * f(nA, nB, nC, x2, y2) <= 0:
return 1
return 0
if f(nA, nB, nC, x1, y1) * f(nA, nB, nC, x2, y2) > 0:
if d1 == r * r or d2 == r * r:
return 1
return 0
else:
return 2
def main():
global a, b, r
a, b, r = map(int, input().split())
n = int(input())
p = [tuple(map(int, input().split())) for _ in range(n)]
ans = 0
for i in range(n):
for j in range(i + 1, n):
x1, y1 = p[i]
x2, y2 = p[j]
ans += count(x1, y1, x2, y2, a, b, r)
cnt = (n - 1) * (n - 2) // 2
ans *= (n - 2)
for i in range(n):
if dist(p[i], (a, b)) == r * r:
ans -= cnt
down = n * (n - 1) * (n - 2) // 6
print('{:.20f}'.format(1.0 * ans / down))
if __name__ == "__main__":
main()