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...
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 Development
Theorem 1 (Parametric intersection). Given two line segments and where , define:
If , the lines containing the segments intersect at the point . This point is an interior point of both segments if and only if and (strictly).
Proof. Points on segment are parameterized as for , and points on segment as for . Setting these equal and solving the linear system:
By Cramer’s rule, the determinant is (with appropriate sign convention), giving the formulas above. The intersection is interior to both segments when and are strictly between 0 and 1, excluding endpoints.
Lemma 1 (Exact arithmetic sufficiency). Since all coordinates , the numerators and denominator in the intersection formulas are integers bounded by . The intersection point can be represented exactly as with , using 64-bit integers.
Proof. The coordinates are integers in . Each term in is a product of two differences of coordinates, each bounded by 499 in absolute value. So . The numerator of the intersection point’s -coordinate is:
where is the numerator of . All quantities fit in 64-bit integers (products of at most three 499-bounded values: ).
Lemma 2 (Distinct point identification). Two intersection points and (in lowest terms with positive denominators) are identical if and only if , , and .
Proof. Rational numbers in lowest terms have a unique representation. By normalizing each coordinate pair to have and appropriately reduced, equality of the triple is equivalent to geometric equality.
Editorial
The algorithm has two phases. First it generates the 5000 segments exactly as prescribed by the Blum Blum Shub recurrence. After that, the task is simply to examine every unordered pair of segments and determine whether their supporting lines meet at an interior point of both segments.
The important implementation choice is to keep every intersection in exact rational form. For a pair of non-parallel segments, the parameters and decide whether the crossing is strictly internal, and the resulting point is stored as a reduced triple representing over a common denominator. Inserting those normalized triples into a set automatically deduplicates repeated geometric intersections coming from different segment pairs.
Pseudocode
Generate the 5000 segments from the pseudorandom sequence.
Create an empty set of normalized intersection points.
For each unordered pair of segments:
Compute the line determinant.
If it is zero, the segments are parallel or collinear, so skip the pair.
Compute the two intersection parameters for the segments.
Keep the pair only when both parameters lie strictly between 0 and 1,
which means the intersection is interior to both segments.
Build the intersection point with integer numerators and denominator.
Normalize the triple by making the denominator positive and dividing by the gcd.
Insert the normalized point into the set.
Return the size of the set.
Complexity Analysis
- Time: where . The number of pairs is . Each pair requires arithmetic and a hash-set insertion. Total: .
- Space: where is the number of distinct interior intersection points, for the hash set. In the worst case .
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#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;
}
"""
Problem 165: Intersections
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.
"""
from math import gcd
def solve():
N = 5000
# Generate segments using BBS
s = 290797
coords = []
for _ in range(4 * N):
s = s * s % 50515093
coords.append(s % 500)
segments = []
for i in range(N):
x1, y1, x2, y2 = coords[4*i], coords[4*i+1], coords[4*i+2], coords[4*i+3]
segments.append((x1, y1, x2, y2))
points = set()
for i in range(N):
x1, y1, x2, y2 = segments[i]
dx1, dy1 = x2 - x1, y2 - y1
for j in range(i + 1, N):
x3, y3, x4, y4 = segments[j]
dx2, dy2 = x4 - x3, y4 - y3
D = dx1 * dy2 - dy1 * dx2
if D == 0:
continue
t_num = (x3 - x1) * dy2 - (y3 - y1) * dx2
u_num = (x3 - x1) * dy1 - (y3 - y1) * dx1
# Interior: 0 < t < 1 and 0 < u < 1
if D > 0:
if not (0 < t_num < D and 0 < u_num < D):
continue
else:
if not (D < t_num < 0 and D < u_num < 0):
continue
# Intersection: (x1 + t_num/D * dx1, y1 + t_num/D * dy1)
# = (x1*D + t_num*dx1, y1*D + t_num*dy1) / D
px = x1 * D + t_num * dx1
py = y1 * D + t_num * dy1
dd = D
if dd < 0:
px, py, dd = -px, -py, -dd
g = gcd(gcd(abs(px), abs(py)), dd)
px //= g
py //= g
dd //= g
points.add((px, py, dd))
print(len(points))
solve()