All Euler problems
Project Euler

Intersections

A segment is uniquely defined by its two endpoints. By considering two line segments in plane geometry there are three possibilities: the segments have zero points, exactly one point, or infinitely...

Source sync Apr 19, 2026
Problem #0165
Level Level 08
Solved By 3,068
Languages C++, Python
Answer 2868868
Length 407 words
geometrymodular_arithmeticnumber_theory

Problem Statement

This archive keeps the full statement, math, and original media on the page.

A segment is uniquely defined by its two endpoints.

By considering two line segments in plane geometry there are three possibilities:

the segments have zero points, one point, or infinitely many points in common.

Moreover when two segments have exactly one point in common it might be the case that that common point is an endpoint of either one of the segments or of both. If a common point of two segments is not an endpoint of either of the segments it is an interior point of both segments.

We will call a common point \(T\) of two segments \(L_1\) and \(L_2\) a true intersection point of \(L_1\) and \(L_2\) if \(T\) is the only common point of \(L_1\) and \(L_2\) and \(T\) is an interior point of both segments.

Consider the three segments \(L_1\), \(L_2\), and \(L_3\):

  • \(L_1\): \((27, 44)\) to \((12, 32)\)

  • \(L_2\): \((46, 53)\) to \((17, 62)\)

  • \(L_3\): \((46, 70)\) to \((22, 40)\)

It can be verified that line segments \(L_2\) and \(L_3\) have a true intersection point. We note that as the one of the end points of \(L_3\): \((22,40)\) lies on \(L_1\) this is not considered to be a true point of intersection. \(L_1\) and \(L_2\) have no common point. So among the three line segments, we find one true intersection point.

Now let us do the same for \(5000\) line segments. To this end, we generate \(20000\) numbers using the so-called "Blum Blum Shub" pseudo-random number generator.

\begin {align*} s_0 &= 290797\\ s_{n + 1} &= s_n \times s_n \pmod {50515093}\\ t_n &= s_n \pmod {500} \end {align*}

To create each line segment, we use four consecutive numbers \(t_n\). That is, the first line segment is given by: \((t_1, t_2)\) to \((t_3, t_4)\).

The first four numbers computed according to the above generator should be: \(27\), \(144\), \(12\) and \(232\). The first segment would thus be \((27,144)\) to \((12,232)\).

How many distinct true intersection points are found among the \(5000\) line segments?

Problem 165: Intersections

Mathematical Foundation

Theorem 1 (Parametric intersection). Given two line segments P1P2P_1P_2 and P3P4P_3P_4 where Pi=(xi,yi)P_i = (x_i, y_i), define:

D=(x1x2)(y3y4)(y1y2)(x3x4)D = (x_1 - x_2)(y_3 - y_4) - (y_1 - y_2)(x_3 - x_4) t=(x1x3)(y3y4)(y1y3)(x3x4)Dt = \frac{(x_1 - x_3)(y_3 - y_4) - (y_1 - y_3)(x_3 - x_4)}{D} u=(x1x2)(y1y3)(y1y2)(x1x3)Du = \frac{(x_1 - x_2)(y_1 - y_3) - (y_1 - y_2)(x_1 - x_3)}{D}

If D0D \neq 0, the lines containing the segments intersect at the point P1+t(P2P1)P_1 + t(P_2 - P_1). This point is an interior point of both segments if and only if 0<t<10 < t < 1 and 0<u<10 < u < 1 (strictly).

Proof. Points on segment P1P2P_1P_2 are parameterized as P1+t(P2P1)P_1 + t(P_2 - P_1) for t[0,1]t \in [0,1], and points on segment P3P4P_3P_4 as P3+u(P4P3)P_3 + u(P_4 - P_3) for u[0,1]u \in [0,1]. Setting these equal and solving the 2×22 \times 2 linear system:

(x1x2)t(x3x4)u=x3x1(x_1 - x_2)t - (x_3 - x_4)u = x_3 - x_1 (y1y2)t(y3y4)u=y3y1(y_1 - y_2)t - (y_3 - y_4)u = y_3 - y_1

By Cramer’s rule, the determinant is D-D (with appropriate sign convention), giving the formulas above. The intersection is interior to both segments when tt and uu are strictly between 0 and 1, excluding endpoints. \square

Lemma 1 (Exact arithmetic sufficiency). Since all coordinates tn{0,1,,499}t_n \in \{0, 1, \ldots, 499\}, the numerators and denominator DD in the intersection formulas are integers bounded by D24992=498,002|D| \leq 2 \cdot 499^2 = 498{,}002. The intersection point (x,y)(x, y) can be represented exactly as (pq,rq)\left(\frac{p}{q}, \frac{r}{q}\right) with gcd(p,q,r)=1\gcd(|p|, |q|, |r|) = 1, using 64-bit integers.

Proof. The coordinates are integers in [0,499][0, 499]. Each term in DD is a product of two differences of coordinates, each bounded by 499 in absolute value. So D24992|D| \leq 2 \cdot 499^2. The numerator of the intersection point’s xx-coordinate is:

p=x1D+tnum(x2x1)p = x_1 \cdot D + t_{\text{num}} \cdot (x_2 - x_1)

where tnumt_{\text{num}} is the numerator of tt. All quantities fit in 64-bit integers (products of at most three 499-bounded values: 4993<227\leq 499^3 < 2^{27}). \square

Lemma 2 (Distinct point identification). Two intersection points (p1q1,r1q1)\left(\frac{p_1}{q_1}, \frac{r_1}{q_1}\right) and (p2q2,r2q2)\left(\frac{p_2}{q_2}, \frac{r_2}{q_2}\right) (in lowest terms with positive denominators) are identical if and only if p1=p2p_1 = p_2, q1=q2q_1 = q_2, and r1=r2r_1 = r_2.

Proof. Rational numbers in lowest terms have a unique representation. By normalizing each coordinate pair (p/q,r/q)(p/q, r/q) to have q>0q > 0 and gcd(p,r,q)\gcd(|p|, |r|, q) appropriately reduced, equality of the triple (p,q,r)(p, q, r) is equivalent to geometric equality. \square

Editorial

Count distinct interior intersection points of 5000 line segments generated by the Blum Blum Shub pseudo-random number generator. Uses exact rational arithmetic to identify distinct points. We generate segments from BBS generator. We then find all interior intersection points. Finally, check 0 < t < 1 and 0 < u < 1 (strictly).

Pseudocode

Generate segments from BBS generator
Find all interior intersection points
Check 0 < t < 1 and 0 < u < 1 (strictly)
else
Compute exact intersection point and normalize
Point = (x1*D + t_num*(x2-x1), y1*D + t_num*(y2-y1)) / D
Normalize: (px/D, py/D) -> reduce to lowest terms

Complexity Analysis

  • Time: O(N2)O(N^2) where N=5000N = 5000. The number of pairs is (50002)=12,497,500\binom{5000}{2} = 12{,}497{,}500. Each pair requires O(1)O(1) arithmetic and a hash-set insertion. Total: O(N2)=O(2.5×107)O(N^2) = O(2.5 \times 10^7).
  • Space: O(K)O(K) where KK is the number of distinct interior intersection points, for the hash set. In the worst case K=O(N2)K = O(N^2).

Answer

2868868\boxed{2868868}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_165/solution.cpp
#include <bits/stdc++.h>
using namespace std;

// Problem 165: Intersections
// Find distinct interior intersection points of 5000 line segments
// generated by the Blum Blum Shub generator.

typedef long long ll;
typedef __int128 lll;

struct Seg {
    ll x1, y1, x2, y2;
};

int main() {
    // Generate segments
    const int N = 5000;
    vector<Seg> segs(N);

    ll s = 290797;
    auto next_s = [&]() -> ll {
        s = (ll)((lll)s * s % 50515093LL);
        return s;
    };

    for (int i = 0; i < N; i++) {
        ll t1 = next_s() % 500;
        ll t2 = next_s() % 500;
        ll t3 = next_s() % 500;
        ll t4 = next_s() % 500;
        segs[i] = {t1, t2, t3, t4};
    }

    // For each pair, find interior intersection
    // Use exact rational arithmetic
    // Store points as (px/d, py/d) reduced to lowest terms with d > 0

    set<tuple<ll,ll,ll,ll>> points;

    auto gcd_func = [](ll a, ll b) -> ll {
        a = abs(a); b = abs(b);
        while (b) { a %= b; swap(a, b); }
        return a;
    };

    for (int i = 0; i < N; i++) {
        ll x1 = segs[i].x1, y1 = segs[i].y1;
        ll x2 = segs[i].x2, y2 = segs[i].y2;
        ll dx1 = x2 - x1, dy1 = y2 - y1;

        for (int j = i + 1; j < N; j++) {
            ll x3 = segs[j].x1, y3 = segs[j].y1;
            ll x4 = segs[j].x2, y4 = segs[j].y2;
            ll dx2 = x4 - x3, dy2 = y4 - y3;

            // D = dx1 * dy2 - dy1 * dx2
            ll D = dx1 * dy2 - dy1 * dx2;
            if (D == 0) continue; // parallel or collinear

            // t_num = (x3 - x1) * dy2 - (y3 - y1) * dx2
            // u_num = (x3 - x1) * dy1 - (y3 - y1) * dx1
            ll t_num = (x3 - x1) * dy2 - (y3 - y1) * dx2;
            ll u_num = (x3 - x1) * dy1 - (y3 - y1) * dx1;

            // Interior: 0 < t < 1 and 0 < u < 1
            // t = t_num / D, u = u_num / D
            // If D > 0: 0 < t_num < D and 0 < u_num < D
            // If D < 0: D < t_num < 0 and D < u_num < 0

            bool t_ok, u_ok;
            if (D > 0) {
                t_ok = (t_num > 0 && t_num < D);
                u_ok = (u_num > 0 && u_num < D);
            } else {
                t_ok = (t_num < 0 && t_num > D);
                u_ok = (u_num < 0 && u_num > D);
            }

            if (!t_ok || !u_ok) continue;

            // Compute intersection point: (x1 + t * dx1, y1 + t * dy1)
            // = (x1 + t_num * dx1 / D, y1 + t_num * dy1 / D)
            // = ((x1 * D + t_num * dx1) / D, (y1 * D + t_num * dy1) / D)

            lll px = (lll)x1 * D + (lll)t_num * dx1;
            lll py = (lll)y1 * D + (lll)t_num * dy1;
            lll dd = D;

            // Normalize: dd > 0
            if (dd < 0) { px = -px; py = -py; dd = -dd; }

            // Reduce to lowest terms
            ll px_l = (ll)px, py_l = (ll)py, dd_l = (ll)dd;
            ll g = gcd_func(gcd_func(abs(px_l), abs(py_l)), dd_l);
            px_l /= g; py_l /= g; dd_l /= g;

            points.insert({px_l, py_l, dd_l, 0});
        }
    }

    cout << points.size() << endl;
    return 0;
}