All Euler problems
Project Euler

Triangles Containing the Origin

Consider all lattice points (integer coordinates) strictly inside or on the circle of radius 105 centered at the origin. How many triangles formed by three such points strictly contain the origin?

Source sync Apr 19, 2026
Problem #0184
Level Level 11
Solved By 1,962
Languages C++, Python
Answer 1725323624056
Length 429 words
geometrylinear_algebramodular_arithmetic

Problem Statement

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

Consider the set \(I_r\) of points \((x,y)\) with integer co-ordinates in the interior of the circle with radius \(r\), centered at the origin, i.e. \(x^2 + y^2 \lt r^2\).

For a radius of \(2\), \(I_2\) contains the nine points \((0,0)\), \((1,0)\), \((1,1)\), \((0,1)\), \((-1,1)\), \((-1,0)\), \((-1,-1)\), \((0,-1)\) and \((1,-1)\). There are eight triangles having all three vertices in \(I_2\) which contain the origin in the interior. Two of them are shown below, the others are obtained from these by rotation.

PIC

For a radius of \(3\), there are \(360\) triangles containing the origin in the interior and having all vertices in \(I_3\) and for \(I_5\) the number is \(10600\).

How many triangles are there containing the origin in the interior and having all three vertices in \(I_{105}\)?

Problem 184: Triangles Containing the Origin

Mathematical Foundation

Theorem 1. A non-degenerate triangle with vertices A,B,CA, B, C (none at the origin) strictly contains the origin if and only if the three vertices, viewed from the origin, do not all lie in any closed half-plane bounded by a line through the origin.

Proof. A point OO lies strictly inside triangle ABCABC if and only if OO is on the same (strict) side of each of the three lines ABAB, BCBC, CACA. Equivalently, viewing A,B,CA, B, C as vectors from OO, the angles AOB\angle AOB, BOC\angle BOC, COA\angle COA (measured as arcs going around) each satisfy the condition that no semicircle contains all three points. If all three points lay in a closed half-plane, the origin would be on or outside the corresponding edge, a contradiction. Conversely, if no half-plane contains all three, the origin must be interior. \square

Theorem 2 (Complementary Counting). Let NN be the number of non-origin lattice points with x2+y2R2x^2 + y^2 \leq R^2. The number of triangles containing the origin equals

(N3)i=1N(ci2)\binom{N}{3} - \sum_{i=1}^{N} \binom{c_i}{2}

where cic_i is the number of points with angle strictly in (θi,θi+π)(\theta_i, \theta_i + \pi) (i.e., in the open semicircle starting just after point ii).

Proof. A triple of non-origin points fails to contain the origin if and only if all three lie in some closed semicircle. We sort all NN points by their angle θi=atan2(yi,xi)\theta_i = \mathrm{atan2}(y_i, x_i). For a “bad” triple {Pa,Pb,Pc}\{P_a, P_b, P_c\} lying in a semicircle, define the “first” point as the one such that proceeding counterclockwise from it, all three points fit within an arc of length π\leq \pi. For each point PiP_i, the points in the open semicircle (θi,θi+π)(\theta_i, \theta_i + \pi) are the candidates for the other two members of a bad triple where PiP_i is first. Thus the number of bad triples is i(ci2)\sum_i \binom{c_i}{2}. Each bad triple is counted exactly once (by its unique “first” point in the angular sweep). \square

Lemma 1. For a circle of radius R=105R = 105, the number of lattice points on or inside the circle (excluding the origin) is N=#{(x,y)Z2:0<x2+y2R2}N = \#\{(x,y) \in \mathbb{Z}^2 : 0 < x^2 + y^2 \leq R^2\}. The two-pointer method computes all cic_i values in O(N)O(N) time after O(NlogN)O(N \log N) sorting.

Proof. After sorting angles, we maintain a pointer jj for each ii marking the farthest point within the semicircle. As ii advances, jj can only advance (the semicircle window slides). Thus the total pointer movement is O(N)O(N). \square

Editorial

Count triangles with integer-coordinate vertices on/inside circle of radius 105 that strictly contain the origin. Method: complementary counting with angular sweep. We collect non-origin lattice points. We then sort by angle. Finally, two-pointer sweep for semicircle counts.

Pseudocode

Collect non-origin lattice points
Sort by angle
Two-pointer sweep for semicircle counts

Complexity Analysis

  • Time: O(NlogN)O(N \log N) for sorting, plus O(N)O(N) for the two-pointer sweep, where Nπ105234,636N \approx \pi \cdot 105^2 \approx 34{,}636 lattice points.
  • Space: O(N)O(N) for storing points and angles.

Answer

1725323624056\boxed{1725323624056}

Code

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

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

int main(){
    const int R = 105;
    const long long R2 = (long long)R * R;

    // Collect non-origin lattice points inside/on circle
    // We'll sort by angle using cross/dot product comparisons to avoid floating point

    // Group points by direction (reduced direction vector)
    // For the semicircle counting, we use a standard approach:
    // Sort by angle, then two-pointer

    struct Point {
        int x, y;
    };

    vector<Point> pts;
    for(int x = -R; x <= R; x++){
        for(int y = -R; y <= R; y++){
            if(x == 0 && y == 0) continue;
            if((long long)x*x + (long long)y*y <= R2){
                pts.push_back({x, y});
            }
        }
    }

    int N = pts.size();

    // Sort by angle using atan2 (careful with precision)
    // Use a comparator based on half-plane then cross product
    auto half = [](const Point& p) -> int {
        // 0 for upper half-plane (y > 0, or y == 0 && x > 0)
        // 1 for lower half-plane
        if(p.y != 0) return (p.y > 0) ? 0 : 1;
        return (p.x > 0) ? 0 : 1;
    };

    sort(pts.begin(), pts.end(), [&](const Point& a, const Point& b){
        int ha = half(a), hb = half(b);
        if(ha != hb) return ha < hb;
        long long cross = (long long)a.x * b.y - (long long)a.y * b.x;
        return cross > 0; // a before b if cross > 0 (a is counterclockwise before b)
    });

    // Two-pointer: for each point i, count points strictly in (angle_i, angle_i + pi)
    // A point j is in the open semicircle (angle_i, angle_i + pi) if:
    //   cross(i, j) > 0 (j is strictly counterclockwise from i)
    //   AND dot-check that j is not past pi from i
    // Actually: j is in (theta_i, theta_i + pi) iff
    //   cross(i, j) > 0  (strictly counterclockwise, less than pi away)

    // Duplicate the array for wraparound
    vector<Point> ext(2 * N);
    for(int i = 0; i < N; i++){
        ext[i] = pts[i];
        ext[i + N] = pts[i]; // wrapped
    }

    // cross(a, b) > 0 means b is strictly CCW from a (angle between 0 and pi)
    // cross(a, b) = 0 means collinear
    // cross(a, b) < 0 means b is CW or past pi

    // For point i, we want j such that cross(pts[i], pts[j % N]) > 0
    // This means pts[j] is in the open semicircle (theta_i, theta_i + pi)

    long long bad = 0;
    int j = 0;
    for(int i = 0; i < N; i++){
        if(j <= i) j = i + 1;
        // Advance j while cross(pts[i], ext[j]) > 0
        while(j < i + N){
            long long cross = (long long)pts[i].x * ext[j].y - (long long)pts[i].y * ext[j].x;
            if(cross <= 0) break;
            j++;
        }
        long long cnt = j - i - 1;
        bad += cnt * (cnt - 1) / 2;
    }

    long long total = (long long)N * (N - 1) * (N - 2) / 6;
    cout << total - bad << endl;

    return 0;
}