0165 交点
* * * *
拉格朗日计划
* * * *
交点

两点能确定一条线段,平面上的两条线段有三种位置关系:没有交点、有唯一交点、有无数个交点。

若两条线段有唯一交点,那么该交点可能是其中至少一条线段的端点,也可能不是任何一条线段的端点,我们称后一种情况为这两条线段的真交点。

例如考虑以下三条线段L1,L2和L3:

L1: (27, 44) 到 (12, 32)

L2: (46, 53) 到 (17, 62)

L3: (46, 70) 到 (22, 40)

可以验证,L2和L3有真交点,而L3的一个端点(22,40)恰好在L1上,因此L3和L1不具有真交点,L1和L2则没有交点。

现在我们用“Blum Blum Shub”伪随机数生成算法生成20000个数。 \begin{align*} s_0&=290797 \\ s_{n+1}&=s_n^2 \pmod{50515093} \\ t_n&=s_n \pmod{500} \end{align*} 从$t_1$开始,每次取连续四项作为一条线段的两个端点,例如第一条线段就是$(t_1,t_2)$到$(t_3,t_4)$,亦即$(27,144)$到$(12,232)$。

如此可得5000条线段,在这5000条线段中共有多少互不相同个真交点?

本题难度:



解答

设$A=(x_1,y_1)$、$B=(x_2,y_2)$是线段的两个端点,则该线段所在直线方程是 $$L_{AB}(x,y)=0,$$ 其中 $$L_{AB}=(x_2-x_1)(y-y_1)-(y_2-y_1)(x-x_1).$$ 若线段AB和线段CD有真交点,则C和D分别在直线AB的两侧,同理A和B也分别在C和D的两侧,即使 $$L_{AB}(C)L_{AB}(D) < 0, \quad L_{CD}(A)L_{CD}(B) < 0.$$ 若一条线段的端点在另一条线段所在的直线上,那么将段点坐标代入直线方程将得到0,因此以上已经排除了这一情形。

若上式成立,那么进一步联立两直线方程以计算交点坐标,此过程即求一2阶矩阵的逆(未学过线性代数的可以消元计算): $$\begin{cases}ax+by=u \\ cx+dy=u\end{cases} \Rightarrow \begin{pmatrix}x \\ y\end{pmatrix}=\frac{1}{ad-bc}\begin{pmatrix}d & -b \\ -c & a\end{pmatrix}\begin{pmatrix}u \\ v\end{pmatrix}=\frac{1}{ad-bc}\begin{pmatrix}du-bv \\ av-cu\end{pmatrix},$$ 按要求生成所有端点,双循环考察所有线段并按真交点的坐标去重后即得结果$2868868$。

注:本题涉及的直线划分平面的知识是我国本科《解析几何》课程的标准内容。

import fractions

s=290797
p=[]
while len(p) < 5000:
    p.append([])
    for i in range(4):
        s=(s*s)%50515093
        p[-1].append(s%500)

def check(p1,p2):
    x1,y1,x2,y2=p1
    a,b,c,d=p2
    return ((x2-x1)*(b-y1)-(y2-y1)*(a-x1))*((x2-x1)*(d-y1)-(y2-y1)*(c-x1)) < 0

def solve(p1,p2):
    x1,y1,x2,y2=p1
    a,b,u=y1-y2,x2-x1,(x2-x1)*y1-(y2-y1)*x1
    x1,y1,x2,y2=p2
    c,d,v=y1-y2,x2-x1,(x2-x1)*y1-(y2-y1)*x1
    return d*u-b*v,a*v-c*u,a*d-b*c


r=set()
for i in range(4999):
    for j in range(i+1,5000):
        if check(p[i],p[j]) and check(p[j],p[i]):
            x,y,z=solve(p[i],p[j])
            r.add((fractions.Fraction(x,z),fractions.Fraction(y,z)))

print len(r)