Thanks to visit codestin.com
Credit goes to tamnd.github.io

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