Project Euler Problem 972

nThe hyperbolic plane can be represented by the open unit disc, namely the set of points (x, y) in Bbb R^2 with x^2 + y^

Project Euler Problem 972

Solution

Answer: 3575508

Let

$$s(P)=x^2+y^2+1.$$

A hyperbolic geodesic in the Poincaré disc is either:

  • a diameter through the origin, or
  • a circle orthogonal to the unit circle.

Such circles have equation

$$x^2+y^2+Ax+By+1=0.$$

Therefore three points $P_i=(x_i,y_i)$ lie on a common geodesic iff there exist $A,B$ such that

$$x_i^2+y_i^2+A x_i+B y_i+1=0$$

for all $i$. Eliminating $A,B$ gives the determinant condition

$$\det \begin{pmatrix} x_1 & y_1 & x_1^2+y_1^2+1\ x_2 & y_2 & x_2^2+y_2^2+1\ x_3 & y_3 & x_3^2+y_3^2+1 \end{pmatrix}=0.$$

So every point corresponds to a projective point

$$(x,y,x^2+y^2+1),$$

and geodesics correspond exactly to projective lines.

The computation strategy is:

  1. Generate all rational points in the unit disc with denominator $\le 12$.
  2. Convert each point to an integer projective vector.
  3. Every pair determines a unique projective line (via cross product).
  4. Count how many points lie on each line.
  5. Sum $k(k-1)(k-2)$ over all lines, because ordered triples are required.

The following C++ program performs the exact computation:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Key {
    ll a,b,c;
};

struct H {
    size_t operator()(Key const& k) const noexcept {
        return ((uint64_t)k.a * 1315423911u)
             ^ ((uint64_t)k.b << 21)
             ^ k.c;
    }
};

struct E {
    bool operator()(Key const& x, Key const& y) const noexcept {
        return x.a==y.a && x.b==y.b && x.c==y.c;
    }
};

ll gcd3(ll a,ll b,ll c){
    return gcd(gcd(llabs(a), llabs(b)), llabs(c));
}

int main() {
    int N = 12;

    vector<pair<int,int>> vals;
    set<pair<int,int>> seen;

    // all reduced rationals with denominator <= N
    for(int q=1;q<=N;q++){
        for(int p=-q+1;p<q;p++){
            int g = gcd(abs(p), q);
            pair<int,int> t = {p/g, q/g};
            if(!seen.count(t)){
                seen.insert(t);
                vals.push_back(t);
            }
        }
    }

    // projective points
    vector<array<int,3>> pts;

    for(auto [p,q] : vals){
        for(auto [r,s] : vals){

            if(1LL*p*p*s*s + 1LL*r*r*q*q < 1LL*q*q*s*s){

                int g = gcd(q,s);
                int l = q/g*s;

                ll X = 1LL*p*(l/q);
                ll Y = 1LL*r*(l/s);

                ll a = X*l;
                ll b = Y*l;
                ll c = X*X + Y*Y + 1LL*l*l;

                ll gg = gcd3(a,b,c);

                pts.push_back({
                    (int)(a/gg),
                    (int)(b/gg),
                    (int)(c/gg)
                });
            }
        }
    }

    unordered_map<Key,int,H,E> mp;
    mp.reserve(30000000);

    int n = pts.size();

    // each pair determines a line
    for(int i=0;i<n;i++){
        auto [a1,b1,c1] = pts[i];

        for(int j=i+1;j<n;j++){

            auto [a2,b2,c2] = pts[j];

            ll A = 1LL*b1*c2 - 1LL*c1*b2;
            ll B = 1LL*c1*a2 - 1LL*a1*c2;
            ll C = 1LL*a1*b2 - 1LL*b1*a2;

            ll g = gcd3(A,B,C);

            A/=g; B/=g; C/=g;

            if(A<0 || (A==0 && (B<0 || (B==0 && C<0)))){
                A=-A; B=-B; C=-C;
            }

            mp[{A,B,C}]++;
        }
    }

    long long ans = 0;

    // if a line has k points, it contributes k(k-1)(k-2)
    for(auto &kv : mp){

        long long v = kv.second;

        // v = C(k,2)
        long long s = sqrtl(1 + 8*v);
        long long k = (1 + s)/2;

        ans += k*(k-1)*(k-2);
    }

    cout << ans << endl;
}

Verification on the examples:

  • $T(2)=24$
  • $T(3)=1296$

both match the statement.

Running the computation for $N=12$ gives:

Answer: 3575508