The circles \(C_0, C_1\) and \(C_2\) are drawn in the picture below.
\(C_0\) and \(C_1\) form a lenticular hole, as well as
\(C_0\) and \(C_2\).
We call an ordered pair of positive real numbers \((r_1, r_2)\) a lenticular pair if
there exist two circles with radii \(r_1\) and \(r_2\) that form a lenticular hole.
We can verify that \((1, 5)\) and
\((5, \sqrt{65})\) are the lenticular
pairs of the example above.
Let \(L(N)\) be the number of
distinct lenticular pairs \((r_1, r_2)\) for which \(0 < r_1 \le r_2 \le N\).
We can verify that \(L(10) = 30\)
and \(L(100) = 3442\).
defget_kmin(x: int, y: int) -> int: """ 计算给定 chord 向量 (x,y) 的最小 k(k_min),使得: 对所有落在 chord 线段 AB 的同侧、且“最靠近 AB 的格点”,圆不包含它们在内部。 从而保证透镜内部不含格点。 几何背景(对应题解的“严格版本”): - 固定两交点 A=(0,0), B=(x,y),圆心在 AB 的垂直平分线上 - 当我们用整数 k 枚举垂直平分线上的格点圆心 M_k 时, k 越大圆心离 AB 越远,对侧圆弓形越薄,越不容易包进格点 - 因此存在最小 k_min,使得对所有 k >= k_min 都“安全” 这里采用的严格计算方法: 对每一列 u = 0..x-1,取该列中“刚好在直线 AB 上方的最邻近格点” P_u = (u, floor(u*y/x) + 1) 对每个 P_u,写出点幂/代入展开得到不等式: F_k(P_u) = C(P_u) + 2k * Delta(P_u) >= 0 其中 Delta(P) = (AB x AP) = v*x - u*y > 0 (保证 P 在 AB 左侧/上方那一侧) C(P) = u^2 + v^2 - u(x+y) + v(x-y) 因此对每个 P_u, k >= ceil( -C(P_u) / (2*Delta(P_u)) ) 取所有列 u 的最大值,即得到 k_min。 为什么只用这些 P_u 就够: 它们构成“紧贴 AB 的那层格点边界”,任何更远的格点需要更厚的圆弓形才包得住, 所以若这层边界都在圆外/圆上,则更远的格点也不会在圆内。 """ km = 0 xp = x + y # 预先缓存 x+y xm = x - y # 预先缓存 x-y
# 枚举 u=0..x-1,构造每一列刚好在 AB 上方的最近格点 P_u for u inrange(x): v = (u * y) // x + 1# floor(u*y/x)+1,确保点在直线 AB 上方(同侧) # C = u^2+v^2 - u(x+y) + v(x-y) # 不等式:C + 2k*Delta >= 0 => k >= -C/(2*Delta) C = u * u + v * v - u * xp + v * xm delta = v * x - u * y # Delta = v*x - u*y,按构造必为正 k = ceil_div(-C, 2 * delta) if k > km: km = k
return km
defget_kmax(b: int, N2: int) -> int: """ 计算给定 b=x^2+y^2 时,满足 r^2 <= N^2 的最大 k(k_max)。 r^2 = b/2 * (2k^2+2k+1) <= N^2 等价于: 2k^2+2k+1 <= floor(2N^2 / b) = s 解二次不等式可得: k <= (sqrt(2s-1)-1)/2 为避免浮点误差,这里用整数 sqrt: t = isqrt(2s-1) k = (t-1)//2 再用 while 做一两步安全修正,确保 rr_from_b_k(b,k) <= N2 且 k+1 不再满足。 """ s = (2 * N2) // b if s <= 0: return -1
# 2k^2+2k+1 <= s => k <= (sqrt(2s-1)-1)/2 t = int_sqrt(2 * s - 1) k = (t - 1) // 2
# 安全修正(理论上不多次循环,通常 0~1 次) while k >= 0and rr_from_b_k(b, k) > N2: k -= 1 while rr_from_b_k(b, k + 1) <= N2: k += 1 return k
defgen_chords(N: int): """ 枚举所有可能产生至少一个可行半径的 chord 向量 (x,y)。 结论(来自几何/格点条件): 1) 交点差向量 (x,y) 必须为奇奇(x,y 均为奇数),否则垂直平分线上没有格点圆心 2) gcd(x,y)=1,否则 AB 上有内部格点,透镜必含格点 利用对称性: chord (x,y) 与 (y,x) 其实是旋转/对称等价,因此只取 1 <= x < y, 另外特殊处理 (1,1)(它不满足 x<y)。 终止条件: 对每个 y(奇数递增),先检查 x=1 是否还存在可行 k 区间; 若 (1,y) 已无可行半径,则更大的 y(以及同 y 的更大 x)也不会有(本题经验上单调),可 break。 """ N2 = N * N chords = []
# 单独加入 (1,1) x = y = 1 b = 2 km = get_kmin(x, y) kx = get_kmax(b, N2) if kx >= km: chords.append((x, y, b, km, kx))
# 枚举 y 为奇数:3,5,7,... y = 3 whileTrue: # 先用 (1,y) 做“是否可以停止”的判定(经验上单调足够) btest = 1 + y * y km_test = get_kmin(1, y) kx_test = get_kmax(btest, N2) if kx_test < km_test: break
# 对固定 y,枚举所有奇数 x < y for x inrange(1, y, 2): if gcd(x, y) != 1: continue
b = x * x + y * y kx = get_kmax(b, N2) if kx < 0: continue
km = get_kmin(x, y) if kx >= km: # 记录该 chord 对应的 b 以及可行 k 区间 [km,kx] chords.append((x, y, b, km, kx))
# Pass 2: 对每个重复 rr,收集它出现的 chord-id 列表 in_map = defaultdict(list) for x, y, b, km, kx in chords: cid = (x << 16) | y # 一个可哈希/可比较的 chord 编码 rr = rr_from_b_k(b, km) for k inrange(km, kx + 1): if rr in multiples: in_map[rr].append(cid) rr += 2 * b * (k + 1)
# 对每个 rr 的 chord 列表 I(rr),对所有子集 T (|T|>=2) 令 c_T++ count = defaultdict(int) for lst in in_map.values(): ids = sorted(set(lst)) add_all_subsets_ge2(ids, count)
# Inclusion-Exclusion 修正: # 对每个子集 T,有 c = c_T 个共同 rr,则它们内部的半径对数为 c*(c+1)/2。 # 若 |T| 为奇数 => 加;若 |T| 为偶数 => 减。 for subset, c in count.items(): add = c * (c + 1) // 2 iflen(subset) % 2 == 0: ans -= add else: ans += add