0295 透镜孔洞
* * * *
拉格朗日计划
* * * *
透镜孔洞

设有相交的两圆,各自的中心和两者的交点都是整点(坐标均为整数),且两者的交集中除交点外再无其它整点,则将这样的交集称为透镜孔洞,例如有以下三个圆 \begin{align*} C_0: &x^2+y^2=25 \\ C_1: &(x+4)^2+(y-4)^2=1 \\ C_2: &(x-12)^2+(y-4)^2=65 \\ \end{align*} 它们各自的交集如下图所示,构成两个个透镜孔洞(红色区域)



如果存在两个半径分别为$r_1$和$r_2$的圆,其交集是透镜孔洞,则称此有序正实数对$(r_1, r_2)$为透镜数对。由上例可知$(1,5)$和$(5,\sqrt{65})$均为透镜数对。

记$L(N)$为所有满足$0 < r_1\le r_2\le N$的不同透镜数对的总数,可以验证$L(10) = 30$以及$L(100)=3442$。求$L(100000)$。

本题难度:



解答

以下解法来自官方论坛,本题似乎应当有更深刻的解法。

设两交点A和B分别在原点和$(a,b)$,则$\gcd(a,b)=1$,否则连线段上就有整点,再考虑该线段的中点可知a,b同为奇数。

简单计算可以发现任何过A和B的圆的圆心都在直线$(a/2,b/2)+(t+1/2)(b,-a)$上,其中$t\in\mathbb Z$是参数,对应的半径是$r^2=(a^2+b^2)(t^2+(t+1)^2)/2$。

过AB的直线将平面分成两部分,一部分H包含圆心,另一部分H'包含透镜,要满足题意,圆中的整点需要都在H内。

枚举$a,b$,并枚举交线中点$(a/2,b/2)$附近的整点,可得$t$的下界,其上界显然。

如此则每组$(a,b)$都有一系列符合要求的半径,两两组合(只需将圆心分别放置在AB两侧即可。不过因对称性枚举时只需考虑圆心在一侧的情况)后去重即得结果$4884650818$。

注:以下代码改编自官方论坛,因math.ceil只在Python 3返回整型,因此代码为Python 3。

import math

bound=100000

class line(object) :
    def __init__(self,P0,P1) :
        try:
            self.m=float(P1)
        except:
            self.m=(P1[1]-P0[1])/(P1[0]-P0[0])           
        self.n=P0[1]-self.m*P0[0]

perp=lambda g,P0:line(P0,-1/g.m)

perp2=lambda P0,P1:perp(line(P0,P1),((P0[0]+P1[0])/2,(P0[1]+P1[1])/2))

cap=lambda g1,g2:((g2.n-g1.n)/(g1.m-g2.m),g1.m*(g2.n-g1.n)/(g1.m-g2.m)+g1.n)

def getCandidate(a,b):
    g=perp2((0,0),(a,b))
    xmax=0.5
    for x in range(1,a) :
        y=(b*x)//a
        if y!=0:
            if a*y==b*x : 
                return []
            g1=perp2((0,0),(x,y))
            P=cap(g,g1)
            xmax=min(P[0],xmax)
    t=math.ceil((a/2-xmax)/b-0.5)
    res=[]
    while True:
        x=(a-b)//2-t*b
        y=(b+a)//2+t*a
        r2=x*x+y*y
        if r2>bound*bound :
            return res
        res.append(r2)
        t+=1

candidates=[]
a=1
finished=False
while a < bound and not finished:
    for b in range(1,a+1,2):
        r=set(getCandidate(a,b))
        if r:
            candidates.append(r)
        elif b==1:
            finished=True
    a+=2

check=lambda s,m,p: m*len(s)*(len(s)+1)//2+sum(check(s & candidates[i],-m,i+1) for i in range(p,len(candidates))) if len(s)>0 else 0
    
print(sum(check(j,1,i+1) for i,j in enumerate(candidates)))