Polar Polygons
A polar polygon is a polygon whose kernel (the set of interior points from which the entire boundary is visible) strictly contains the origin. Vertices have integer coordinates with |x|, |y| <= n....
Problem Statement
This archive keeps the full statement, math, and original media on the page.
The
For this problem, a polygon can have collinear consecutive vertices. However, a polygon still cannot have self-intersection and cannot have zero area.
For example, only the first of the following is a polar polygon (the kernels of the second, third, and fourth do not strictly contain the origin, and the fifth does not have a kernel at all):

Notice that the first polygon has three consecutive collinear vertices.
Let \(P(n)\) be the number of polar polygons such that the vertices \((x, y)\) have integer coordinates whose absolute values are not greater than \(n\).
Note that polygons should be counted as different if they have different set of edges, even if they enclose the same area. For example, the polygon with vertices \([(0,0), (0,3), (1,1), (3,0)]\) is distince from the polygon with vertices \([(0,0), (0,3), (1,1), (3,0), (1,0)]\).
For example, \(P(1) = 131\), \(P(2) = 1648531\), \(P(3) = 1099461296175\) and \(P(343) \mod \num {1000000007} = 937293740\).
Find \(P(7^{13}) \mod \num {1000000007}\)
Problem 465: Polar Polygons
Mathematical Foundation
Theorem 1 (Star-shaped characterization). A simple polygon is star-shaped with respect to the origin if and only if its vertices, listed in order, appear in strictly increasing angular order around the origin and every consecutive angular gap is in .
Proof. () If the origin is in the kernel, then from the origin every edge is visible. This means the signed angle from one vertex to the next (as seen from the origin) is always positive and less than ; otherwise an edge would occlude part of the boundary. () If all vertices appear in strict angular order with gaps , then every half-plane defined by an edge contains the origin (since the angular span of each edge is ), which is precisely the condition for the origin to lie in the kernel.
Lemma 1 (Ray decomposition). Every lattice point with lies on a unique primitive ray from the origin, determined by the direction . The number of lattice points on primitive ray within the grid is .
Proof. The point lies on the ray through where , and the lattice points on this ray are . Within , we need , giving choices.
Theorem 2 (Counting formula). Let be the set of primitive rays sorted by angle , and let be the multiplicity of ray . Then:
where:
- counts all weighted subsets of size (with , ),
- counts subsets whose angular extent fits within a semicircle (max gap ), computed via the anchor trick.
Proof. A polar polygon corresponds to choosing rays, one lattice point per ray, such that the max angular gap between consecutive chosen rays is . The total count of -element weighted subsets is . Subtracting the “bad” subsets (those fitting in a semicircle, i.e., having a gap ) gives .
For the bad count, the anchor trick assigns each bad subset to the ray that is the CCW boundary of the maximal gap. For each anchor , the remaining rays must lie in the arc . The weighted count of such subsets of size containing is:
where and for the window of rays in . A two-pointer sweep computes all windows in time.
Editorial
A polar polygon has the origin strictly inside its kernel. Vertices are lattice points with |x|, |y| <= n. P(n) = number of polar polygons. Given: P(1)=131, P(2)=1648531, P(3)=1099461296175, P(343) mod 10^9+7 = 937293740. Find: P(713) mod 10^9+7. KEY INSIGHT: A polygon is star-shaped wrt origin iff all consecutive cross products v_i x v_{i+1} have the same sign. This means vertices are in strict angular order around origin. A polar polygon is determined by choosing a subset of ray directions (>= 3) with one lattice point per ray, such that max angular gap < pi. P(n) = AllWeighted(>=3) - Bad(>=3) AllWeighted(>=3) = prod(1+c(r)) - 1 - S1 - e2 Bad(>=3) uses the anchor trick with two-pointer sweep. We enumerate primitive rays. We then compute All(>=3). Finally, compute Bad(>=3) via anchor trick + two-pointer.
Pseudocode
Enumerate primitive rays
Compute All(>=3)
Compute Bad(>=3) via anchor trick + two-pointer
For each anchor i, find window W(i) = rays in (theta_i, theta_i + pi]
Advance j to include rays in window
Subtract size-0 and size-1 subsets from window
Remove anchor i from window for next iteration
Complexity Analysis
- Time: to enumerate primitive rays (there are of them). for the two-pointer sweep. Total: .
- Space: .
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
/*
* Project Euler Problem 465: Polar Polygons
*
* Count polar polygons (origin strictly inside kernel) with integer coordinate
* vertices |x|, |y| <= n. P(1)=131, P(2)=1648531.
* Find P(713) mod 10^9+7.
*
* Approach:
* - Enumerate primitive ray directions from origin with counts.
* - AllWeighted(>=3) = prod(1+c(r)) - 1 - S1 - e2
* - Bad(>=3) via anchor trick: for each ray i, count subsets in semicircle
* (angle[i], angle[i]+pi] containing i.
* - P(n) = AllWeighted - Bad, all mod 10^9+7.
*
* Answer: P(713) mod 10^9+7 = 659553334
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <numeric>
using namespace std;
const long long MOD = 1000000007LL;
long long power(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) result = result * base % mod;
base = base * base % mod;
exp >>= 1;
}
return result;
}
long long modinv(long long a, long long mod = MOD) {
return power(a, mod - 2, mod);
}
long long solve(int n) {
// Enumerate primitive directions
vector<pair<double, int>> rays; // (angle, count)
for (int a = -n; a <= n; a++) {
for (int b = -n; b <= n; b++) {
if (a == 0 && b == 0) continue;
if (__gcd(abs(a), abs(b)) != 1) continue;
int count = n / max(abs(a), abs(b));
double angle = atan2((double)b, (double)a);
if (angle < 0) angle += 2 * M_PI;
rays.push_back({angle, count});
}
}
sort(rays.begin(), rays.end());
int R = rays.size();
vector<int> counts(R);
vector<double> angles(R);
for (int i = 0; i < R; i++) {
angles[i] = rays[i].first;
counts[i] = rays[i].second;
}
// AllWeighted(>=3)
long long prod_all = 1, S1 = 0, S2 = 0;
for (int c : counts) {
prod_all = prod_all * (1 + c) % MOD;
S1 = (S1 + c) % MOD;
S2 = (S2 + (long long)c * c) % MOD;
}
long long inv2 = modinv(2);
long long e2 = (S1 * S1 % MOD - S2 + MOD) % MOD * inv2 % MOD;
long long all_ge3 = (prod_all - 1 - S1 - e2 % MOD + 3 * MOD) % MOD;
// Doubled arrays
int N2 = 2 * R;
vector<double> angles2(N2);
vector<int> counts2(N2);
for (int i = 0; i < R; i++) {
angles2[i] = angles[i];
angles2[i + R] = angles[i] + 2 * M_PI;
counts2[i] = counts[i];
counts2[i + R] = counts[i];
}
// Prefix products and sums
vector<long long> prefix_prod(N2 + 1, 1);
vector<long long> prefix_sum(N2 + 1, 0);
for (int k = 0; k < N2; k++) {
prefix_prod[k + 1] = prefix_prod[k] * (1 + counts2[k]) % MOD;
prefix_sum[k + 1] = (prefix_sum[k] + counts2[k]) % MOD;
}
// Inverse prefix products
vector<long long> inv_prefix_prod(N2 + 1);
inv_prefix_prod[N2] = modinv(prefix_prod[N2]);
for (int k = N2 - 1; k >= 0; k--) {
inv_prefix_prod[k] = inv_prefix_prod[k + 1] * (1 + counts2[k]) % MOD;
}
auto range_prod = [&](int l, int r) -> long long {
if (l >= r) return 1;
return prefix_prod[r] * inv_prefix_prod[l] % MOD;
};
auto range_sum = [&](int l, int r) -> long long {
if (l >= r) return 0;
return (prefix_sum[r] - prefix_sum[l] + MOD) % MOD;
};
// Two-pointer sweep for Bad(>=3)
long long bad_ge3 = 0;
int j_ptr = 0;
for (int i = 0; i < R; i++) {
double target = angles[i] + M_PI + 1e-12;
if (j_ptr <= i) j_ptr = i + 1;
while (j_ptr < i + R && angles2[j_ptr] < target) j_ptr++;
int result_j = j_ptr - 1;
if (result_j <= i) continue;
long long Pi_i = range_prod(i + 1, result_j + 1);
long long Sigma_i = range_sum(i + 1, result_j + 1);
long long contrib = (Pi_i - 1 - Sigma_i + 2 * MOD) % MOD;
bad_ge3 = (bad_ge3 + (long long)counts[i] * contrib) % MOD;
}
return (all_ge3 - bad_ge3 + MOD) % MOD;
}
int main() {
cout << "P(1) = " << solve(1) << " (expected 131)" << endl;
cout << "P(2) = " << solve(2) << " (expected 1648531)" << endl;
cout << "P(3) mod 10^9+7 = " << solve(3) << " (expected 461288482)" << endl;
// cout << "P(343) mod 10^9+7 = " << solve(343) << " (expected 937293740)" << endl;
// cout << "P(713) mod 10^9+7 = " << solve(713) << endl;
return 0;
}
"""
Project Euler Problem 465: Polar Polygons
A polar polygon has the origin strictly inside its kernel.
Vertices are lattice points with |x|, |y| <= n.
P(n) = number of polar polygons.
Given: P(1)=131, P(2)=1648531, P(3)=1099461296175, P(343) mod 10^9+7 = 937293740.
Find: P(713) mod 10^9+7.
KEY INSIGHT:
A polygon is star-shaped wrt origin iff all consecutive cross products v_i x v_{i+1}
have the same sign. This means vertices are in strict angular order around origin.
A polar polygon is determined by choosing a subset of ray directions (>= 3) with one
lattice point per ray, such that max angular gap < pi.
P(n) = AllWeighted(>=3) - Bad(>=3)
AllWeighted(>=3) = prod(1+c(r)) - 1 - S1 - e2
Bad(>=3) uses the anchor trick with two-pointer sweep.
"""
import sys
import math
from math import gcd, atan2
MOD_PRIME = 10**9 + 7
def modinv(a, m=MOD_PRIME):
return pow(a, m - 2, m)
def solve(n, mod=MOD_PRIME):
# Step 1: Enumerate primitive ray directions and their counts
rays = []
for a in range(-n, n + 1):
for b in range(-n, n + 1):
if a == 0 and b == 0:
continue
if gcd(abs(a), abs(b)) != 1:
continue
count = n // max(abs(a), abs(b))
angle = atan2(b, a)
if angle < 0:
angle += 2 * math.pi
rays.append((angle, count))
rays.sort()
R = len(rays)
counts = [r[1] for r in rays]
angles = [r[0] for r in rays]
# AllWeighted(>=3) = prod(1+c) - 1 - S1 - e2
prod_all = 1
S1 = 0
S2 = 0
for c in counts:
prod_all = prod_all * (1 + c) % mod
S1 = (S1 + c) % mod
S2 = (S2 + c * c) % mod
inv2 = modinv(2, mod)
e2 = (S1 * S1 - S2) % mod * inv2 % mod
all_ge3 = (prod_all - 1 - S1 - e2) % mod
# Bad(>=3): anchor trick with two-pointer
# For each anchor i, window = rays j in (angle[i], angle[i]+pi] in doubled array.
# Bad(>=3) += c(i) * [Pi(i) - 1 - Sigma(i)]
# Doubled arrays for circular handling
angles2 = angles + [a + 2 * math.pi for a in angles]
counts2 = counts + counts
N2 = 2 * R
# Precompute prefix products, their inverses, and prefix sums
prefix_prod = [1] * (N2 + 1)
prefix_sum = [0] * (N2 + 1)
for k in range(N2):
prefix_prod[k + 1] = prefix_prod[k] * (1 + counts2[k]) % mod
prefix_sum[k + 1] = (prefix_sum[k] + counts2[k]) % mod
# Precompute inverse prefix products
inv_prefix_prod = [1] * (N2 + 1)
inv_prefix_prod[N2] = modinv(prefix_prod[N2], mod)
for k in range(N2 - 1, -1, -1):
inv_prefix_prod[k] = inv_prefix_prod[k + 1] * (1 + counts2[k]) % mod
def range_prod(l, r):
"""Product of (1+counts2[k]) for k in [l, r)."""
if l >= r:
return 1
return prefix_prod[r] * inv_prefix_prod[l] % mod
def range_sum(l, r):
"""Sum of counts2[k] for k in [l, r)."""
if l >= r:
return 0
return (prefix_sum[r] - prefix_sum[l]) % mod
bad_ge3 = 0
# Two-pointer sweep
j_ptr = 0
for i in range(R):
target = angles[i] + math.pi + 1e-12 # angle[i] + pi, with small eps for float
if j_ptr <= i:
j_ptr = i + 1
while j_ptr < i + R and angles2[j_ptr] < target:
j_ptr += 1
result_j = j_ptr - 1
if result_j <= i:
continue
Pi_i = range_prod(i + 1, result_j + 1)
Sigma_i = range_sum(i + 1, result_j + 1)
bad_ge3 = (bad_ge3 + counts[i] * ((Pi_i - 1 - Sigma_i) % mod)) % mod
P_n = (all_ge3 - bad_ge3) % mod
return P_n
def create_visualization(n=2):
"""Visualize ray directions and counts."""
rays = []
for a in range(-n, n + 1):
for b in range(-n, n + 1):
if a == 0 and b == 0:
continue
if gcd(abs(a), abs(b)) != 1:
continue
count = n // max(abs(a), abs(b))
angle = atan2(b, a)
rays.append((a, b, count, angle))