Problem Summary

Let

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

A polar polygon is obtained by choosing lattice points from \(\mathcal{L}_n\), taking at most one vertex on each directed ray from the origin, and reading the chosen vertices in increasing polar angle. The geometric condition used by the implementations is that the origin must lie in the convex hull of the chosen vertices. Equivalently, the chosen directions must not all lie inside one closed half-plane through the origin.

The task is to compute the number \(P(n)\) of such polygons modulo

$$M=10^9+7.$$

The implementations also contain the checkpoints

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

Mathematical Approach

The key simplification is to forget the exact coordinates first and classify vertices by direction. Every directed primitive ray carries a certain number of lattice points, and only that number matters for counting.

Step 1: Replace Lattice Points by Weighted Rays

Every nonzero lattice point can be written uniquely as

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

where \((a,b)\) gives a primitive direction. Define the level of that direction by

$$k=\max(|a|,|b|).$$

The points on this directed ray that still lie in \(\mathcal{L}_n\) are exactly

$$t(a,b)\qquad\text{for}\qquad 1\le t\le \left\lfloor\frac{n}{k}\right\rfloor.$$

So the ray contributes

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

available lattice points. A valid polygon can use at most one vertex on a fixed directed ray, because two points on the same ray are collinear with the origin and the nearer one cannot be an extreme vertex. Therefore one directed ray contributes exactly

$$1+w(k)$$

choices: choose nothing, or choose one of its \(w(k)\) lattice points.

Step 2: Count Primitive Directions on One Square Shell

Fix \(k\ge 1\). Primitive directions of level \(k\) are precisely the visible lattice points on the boundary of the square \(\max(|x|,|y|)=k\).

In one octant there are \(\varphi(k)\) such directions, so symmetry gives

$$8\varphi(k)$$

directed primitive rays on that shell, or

$$4\varphi(k)$$

opposite-direction pairs. Every pair on level \(k\) has the same weight \(w(k)=\lfloor n/k\rfloor\).

Step 3: Group Shells by Equal Weight

The weight depends only on the quotient \(\left\lfloor n/k\right\rfloor\), not on the direction itself. If that quotient is constant and equal to \(u\) for all \(k\) in an interval \([a,b]\), then the number of opposite pairs with weight \(u\) is

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

Introduce the summatory totient function

$$\Phi(x)=\sum_{m\le x}\varphi(m).$$

Then every quotient block contributes

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

This is the reason the problem collapses to only \(O(\sqrt n)\) distinct blocks: the values of \(\left\lfloor n/k\right\rfloor\) repeat on long intervals.

Step 4: Count All Selections Before Enforcing the Polygon Condition

Let the opposite pairs have weights \(w_1,\dots,w_m\). For one pair there are four types of choices:

For one pair the four possibilities are: choose nothing, choose one point on the first ray, choose one point on the opposite ray, or choose one point on each ray.

Hence one pair contributes

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

If we define

$$H=\prod_{i=1}^{m}(1+w_i),$$

then the total weighted count of all ray selections is

$$T=H^2.$$

After quotient blocking, the same product becomes

$$H=\prod_{u}(u+1)^{c(u)}.$$

Step 5: Subtract Selections Contained in a Closed Half-Plane

A selection fails precisely when all chosen directions lie in some closed semicircle. Count such bad selections by choosing their first selected boundary direction.

Fix one directed boundary ray with weight \(w_i\). We must choose a point on that boundary ray, giving \(w_i\) possibilities. Every other opposite pair contributes exactly one direction inside the following open semicircle, so it contributes a factor \(1+w_j\). The opposite boundary ray itself may be used or skipped, contributing another factor \(1+w_i\). Therefore the count anchored at this directed boundary is

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

Summing over both orientations of every opposite pair yields

$$2H\sum_{i=1}^{m}w_i.$$

However, the subset consisting of one point on both rays of the same opposite pair is counted twice, once from each boundary orientation. The total correction for these double counts is

$$\sum_{i=1}^{m} w_i^2.$$

Adding the empty selection gives

$$B=1+2HA_1-A_2,$$

where

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

Therefore

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

Step 6: Final Formula in Quotient Blocks

Because all pairs in one block have the same weight \(u\), the two moments become

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

Substituting into the previous formula gives the exact expression used by the implementations:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

Worked Example: \(n=2\)

For \(n=2\) there are only two shell levels:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

The level \(k=1\) shell contributes

$$4\varphi(1)=4$$

opposite pairs of weight \(2\), and the level \(k=2\) shell contributes

$$4\varphi(2)=4$$

opposite pairs of weight \(1\). Hence

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

So

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

which matches the checkpoint.

How the Code Works

The C++, Python, and Java implementations all follow the same mathematical pipeline. First they choose a threshold near \(n^{2/3}\), compute \(\varphi(1),\dots,\varphi(B)\) with a sieve, and store the prefix values of \(\Phi(x)\) for every \(x\le B\).

For larger arguments they use memoized divide-block recursion based on the identity

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2},$$

which leads to

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

with equal quotient values grouped into one interval. That provides fast access to \(\Phi(x)\) on the boundaries of every quotient block.

Next the implementations enumerate the blocks on which \(\lfloor n/k\rfloor\) is constant, compute the multiplicity \(c(u)\) from two summatory-totient queries, and update the product \(H\) together with the first and second moments \(A_1\) and \(A_2\). The multiplicities are kept exactly for modular exponentiation, while the additive moments are accumulated modulo \(M\).

The C++ and Java implementations optionally split the quotient blocks into chunks, compute partial products and partial moments independently, and merge them at the end. The Python implementation performs the same reduction in one serial pass.

Complexity Analysis

Let \(B\) denote the sieve cutoff, chosen on the order of \(n^{2/3}\). Building the totient table and the small prefix values costs \(O(B)\) time and \(O(B)\) memory. The memoized summatory-totient stage uses divisor-block grouping, so only the distinct quotient values need to be explored; this is the standard \(O(n^{2/3})\)-type approach for one top-level query. The final quotient-block traversal contributes only \(O(\sqrt n)\) additional arithmetic. In practice the totient stage dominates, and parallel block folding improves wall-clock time in the threaded implementations without changing the asymptotic bound.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=465
  2. Euler totient function: Wikipedia - Euler totient function
  3. Summatory totient function: MathWorld - Totient Summatory Function
  4. Visible lattice points and Farey-type counting: Wikipedia - Farey sequence
  5. Convex hull and half-plane containment: Wikipedia - Convex hull

Problemzusammenfassung

Sei

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

Ein Polarpolygon entsteht, indem man Gitterpunkte aus \(\mathcal{L}_n\) auswählt, auf jedem gerichteten Strahl vom Ursprung höchstens einen Eckpunkt zulässt und die gewählten Punkte nach wachsendem Polarwinkel anordnet. Die von den Implementierungen benutzte geometrische Bedingung lautet: Der Ursprung muss in der konvexen Hülle der gewählten Punkte liegen. Äquivalent dazu dürfen die gewählten Richtungen nicht vollständig in einer abgeschlossenen Halbebene durch den Ursprung liegen.

Gesucht ist also die Anzahl \(P(n)\) solcher Polygone modulo

$$M=10^9+7.$$

Als Kontrollwerte werden verwendet

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

Mathematischer Ansatz

Die entscheidende Vereinfachung besteht darin, zuerst die exakten Koordinaten zu vergessen und stattdessen nach Richtungen zu klassifizieren. Jeder primitive gerichtete Strahl trägt eine gewisse Anzahl von Gitterpunkten, und genau diese Anzahl steuert die Zählung.

Schritt 1: Gitterpunkte durch gewichtete Strahlen ersetzen

Jeder von null verschiedene Gitterpunkt besitzt eine eindeutige Darstellung

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

wobei \((a,b)\) eine primitive Richtung beschreibt. Das zugehörige Niveau sei

$$k=\max(|a|,|b|).$$

Auf diesem gerichteten Strahl liegen genau die Punkte aus \(\mathcal{L}_n\), für die

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor$$

gilt. Somit trägt der Strahl

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

verfügbare Gitterpunkte. Ein gültiges Polygon kann auf einem festen gerichteten Strahl höchstens einen Eckpunkt benutzen, denn zwei Punkte auf demselben Strahl sind mit dem Ursprung kollinear und der nähere Punkt kann kein Extrempunkt sein. Daher liefert ein gerichteter Strahl genau

$$1+w(k)$$

Möglichkeiten: nichts wählen oder einen der \(w(k)\) Punkte wählen.

Schritt 2: Primitive Richtungen auf einer Quadrat-Schale zählen

Fixiere \(k\ge 1\). Primitive Richtungen des Niveaus \(k\) sind genau die sichtbaren Gitterpunkte auf dem Rand des Quadrats \(\max(|x|,|y|)=k\).

In einem Oktanten gibt es \(\varphi(k)\) solche Richtungen, also insgesamt

$$8\varphi(k)$$

gerichtete primitive Strahlen auf dieser Schale beziehungsweise

$$4\varphi(k)$$

Paare entgegengesetzter Richtungen. Jedes dieser Paare besitzt das gleiche Gewicht \(w(k)=\lfloor n/k\rfloor\).

Schritt 3: Schalen mit gleichem Gewicht zusammenfassen

Das Gewicht hängt nur vom Quotienten \(\lfloor n/k\rfloor\) ab, nicht von der Richtung selbst. Ist dieser Quotient für alle \(k\) in einem Intervall \([a,b]\) konstant und gleich \(u\), dann ist die Anzahl der entgegengesetzten Paare mit Gewicht \(u\)

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

Mit der summatorischen Phi-Funktion

$$\Phi(x)=\sum_{m\le x}\varphi(m)$$

wird daraus

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

Genau deshalb schrumpft das Problem auf nur \(O(\sqrt n)\) verschiedene Blöcke: Die Werte von \(\lfloor n/k\rfloor\) wiederholen sich auf langen Intervallen.

Schritt 4: Alle Auswahlen vor der Polygon-Bedingung zählen

Seien \(w_1,\dots,w_m\) die Gewichte der Paare entgegengesetzter Richtungen. Für ein einzelnes Paar gibt es vier Typen von Entscheidungen:

Für ein einzelnes Paar gibt es vier Möglichkeiten: nichts wählen, einen Punkt auf dem ersten Strahl wählen, einen Punkt auf dem Gegenstrahl wählen oder auf beiden Strahlen je einen Punkt wählen.

Daher trägt ein Paar den Faktor

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

Definiert man

$$H=\prod_{i=1}^{m}(1+w_i),$$

dann ist die gesamte gewichtete Zahl aller Strahl-Auswahlen

$$T=H^2.$$

Nach Quotientenblöcken geschrieben ergibt sich

$$H=\prod_{u}(u+1)^{c(u)}.$$

Schritt 5: Auswahlen in einer abgeschlossenen Halbebene abziehen

Eine Auswahl ist genau dann ungültig, wenn alle gewählten Richtungen in einem abgeschlossenen Halbkreis liegen. Solche schlechten Auswahlen zählen wir über ihre erste gewählte Randrichtung.

Fixiere einen gerichteten Randstrahl mit Gewicht \(w_i\). Auf diesem Randstrahl muss ein Punkt gewählt werden, also gibt es \(w_i\) Möglichkeiten. Jedes andere Paar liefert genau eine Richtung im folgenden offenen Halbkreis und damit den Faktor \(1+w_j\). Der entgegengesetzte Randstrahl selbst darf zusätzlich benutzt oder ausgelassen werden und liefert daher einen weiteren Faktor \(1+w_i\). Insgesamt erhält man für diesen verankerten Rand

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

Summiert über beide Orientierungen jedes entgegengesetzten Paares ergibt das

$$2H\sum_{i=1}^{m}w_i.$$

Dabei wird jedoch die Auswahl, die genau aus einem Punkt auf beiden Strahlen desselben Paares besteht, doppelt gezählt, nämlich einmal von jeder Randorientierung aus. Die nötige Korrektur ist also

$$\sum_{i=1}^{m} w_i^2.$$

Zusammen mit der leeren Auswahl folgt

$$B=1+2HA_1-A_2,$$

wobei

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

Damit gilt

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

Schritt 6: Endformel in Quotientenblöcken

Da alle Paare innerhalb eines Blocks dasselbe Gewicht \(u\) besitzen, werden die beiden Momente zu

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

Einsetzen liefert genau die Formel, die in den Implementierungen verwendet wird:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

Durchgerechnetes Beispiel: \(n=2\)

Für \(n=2\) gibt es nur zwei Schalen:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

Die Schale \(k=1\) liefert

$$4\varphi(1)=4$$

Paare entgegengesetzter Richtungen mit Gewicht \(2\), und die Schale \(k=2\) liefert

$$4\varphi(2)=4$$

Paare mit Gewicht \(1\). Also

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

Daraus folgt

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

genau der Kontrollwert.

Wie der Code arbeitet

Die C++-, Python- und Java-Implementierungen folgen alle derselben mathematischen Pipeline. Zuerst wird eine Schranke in der Größenordnung \(n^{2/3}\) gewählt, dann werden \(\varphi(1),\dots,\varphi(B)\) per Sieb berechnet und die Präfixwerte von \(\Phi(x)\) für alle \(x\le B\) gespeichert.

Für größere Argumente wird eine memoisiert berechnete Divisionsblock-Rekursion auf Basis der Identität

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2}$$

verwendet. Daraus folgt

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

wobei gleiche Quotientenwerte intervallweise zusammengefasst werden. So kann \(\Phi(x)\) schnell an den Rändern der Quotientenblöcke abgefragt werden.

Anschließend werden alle Blöcke mit konstantem \(\lfloor n/k\rfloor\) durchlaufen, aus zwei Summenwerten von \(\Phi\) die Vielfachheit \(c(u)\) bestimmt und daraus das Produkt \(H\) sowie die beiden Momente \(A_1\) und \(A_2\) aktualisiert. Die Blockvielfachheiten werden für die modulare Potenzierung exakt gehalten, die additiven Momente aber direkt modulo \(M\) aufsummiert.

Die C++- und Java-Implementierungen können die Quotientenblöcke in unabhängige Teilstücke zerlegen, partielle Produkte und Momente parallel berechnen und am Ende zusammenführen. Die Python-Implementierung führt dieselbe Reduktion seriell aus.

Komplexitätsanalyse

Sei \(B\) die Siebgrenze, also von Größenordnung \(n^{2/3}\). Das Erzeugen der Totient-Tabelle und der kleinen Präfixwerte kostet \(O(B)\) Zeit und \(O(B)\) Speicher. Die memoisiert berechnete summatorische Phi-Funktion nutzt Divisionsblöcke, sodass nur die verschiedenen Quotientenwerte betrachtet werden müssen; das ist der klassische \(O(n^{2/3})\)-artige Ansatz für eine einzelne Top-Level-Abfrage. Die abschließende Traversierung der Quotientenblöcke benötigt nur noch \(O(\sqrt n)\) zusätzliche Arithmetik. Praktisch dominiert daher die Totient-Stufe, und die Parallelisierung verkürzt nur die Laufzeit der Blockfaltung, nicht aber die asymptotische Schranke.

Fußnoten und Referenzen

  1. Problemseite: https://projecteuler.net/problem=465
  2. Eulersche Phi-Funktion: Wikipedia - Eulersche Phi-Funktion
  3. Summatorische Totient-Funktion: MathWorld - Totient Summatory Function
  4. Sichtbare Gitterpunkte und Farey-artige Zählung: Wikipedia - Farey-Folge
  5. Konvexe Hülle und Halbebenen-Kriterium: Wikipedia - Konvexe Hülle

Problem Özeti

Şunu tanımlayalım:

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

Bir polar çokgen, \(\mathcal{L}_n\) içinden kafes noktaları seçilip, orijinden çıkan her yönlü ışın üzerinde en fazla bir köşe bırakılarak ve seçilen noktalar artan kutupsal açıya göre sıralanarak elde edilir. Uygulamaların kullandığı geometrik koşul şudur: Orijin seçilen köşelerin dışbükey zarfının içinde kalmalıdır. Bunun eşdeğeri, seçilen yönlerin orijinden geçen tek bir kapalı yarı düzlem içine sığmamasıdır.

Dolayısıyla amaç, bu tür çokgenlerin sayısı olan \(P(n)\) değerini

$$M=10^9+7$$

modunda hesaplamaktır.

Uygulamalardaki kontrol değerleri şunlardır:

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

Matematiksel Yaklaşım

Temel fikir, önce kesin koordinatları unutup noktaları yalnızca yönlerine göre sınıflandırmaktır. Her ilkel yönlü ışın belirli sayıda kafes noktası taşır ve sayımı belirleyen tek şey de bu sayıdır.

Adım 1: Kafes Noktalarını Ağırlıklı Işınlara Dönüştür

Sıfırdan farklı her kafes noktası tekil olarak

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1$$

şeklinde yazılır; burada \((a,b)\) ilkel bir yön verir. Bu yönün seviyesi

$$k=\max(|a|,|b|)$$

olsun. Bu yönlü ışın üzerinde \(\mathcal{L}_n\) içinde kalan noktalar tam olarak

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor$$

koşulunu sağlayanlardır. O halde ışının taşıdığı kullanılabilir nokta sayısı

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

olur. Geçerli bir çokgen, sabit bir yönlü ışın üzerinde en fazla bir köşe kullanabilir; çünkü aynı ışın üzerindeki iki nokta orijinle doğrusal olur ve içte kalan nokta uç köşe olamaz. Bu yüzden bir yönlü ışın tam olarak

$$1+w(k)$$

seçenek verir: hiç nokta seçmemek ya da mevcut \(w(k)\) noktadan birini seçmek.

Adım 2: Bir Kare Kabuğundaki İlkel Yönleri Say

\(k\ge 1\) sabit olsun. Seviyesi \(k\) olan ilkel yönler, \(\max(|x|,|y|)=k\) karesinin sınırındaki görünür kafes noktalarıdır.

Bir oktantta \(\varphi(k)\) tane böyle yön vardır; simetriyle toplam

$$8\varphi(k)$$

yönlü ilkel ışın, yani

$$4\varphi(k)$$

zıt yön çifti elde edilir. Bu kabuktaki her çiftin ağırlığı aynıdır:

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor.$$

Adım 3: Aynı Ağırlığa Sahip Kabukları Birleştir

Ağırlık yalnızca \(\lfloor n/k\rfloor\) bölümüne bağlıdır; yönün kendisine bağlı değildir. Bu bölüm \([a,b]\) aralığındaki tüm \(k\) değerleri için sabit ve \(u\) olsun. O zaman ağırlığı \(u\) olan zıt yön çiftlerinin sayısı

$$c(u)=4\sum_{k=a}^{b}\varphi(k)$$

olur. Burada toplam totient fonksiyonunu

$$\Phi(x)=\sum_{m\le x}\varphi(m)$$

olarak tanımlarsak

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr)$$

elde edilir. Böylece problem yalnızca \(O(\sqrt n)\) kadar farklı bloğa iner; çünkü \(\lfloor n/k\rfloor\) uzun aralıklarda tekrar eder.

Adım 4: Çokgen Koşulunu Uygulamadan Önce Tüm Seçimleri Say

Zıt yön çiftlerinin ağırlıkları \(w_1,\dots,w_m\) olsun. Tek bir çift için dört tür seçim vardır:

Tek bir çift için dört olasılık vardır: hiç seçim yapmamak, birinci ışından bir nokta seçmek, karşı ışından bir nokta seçmek ya da iki ışından da birer nokta seçmek.

Dolayısıyla bir çiftin katkısı

$$1+w_i+w_i+w_i^2=(1+w_i)^2$$

olur. Eğer

$$H=\prod_{i=1}^{m}(1+w_i)$$

tanımlanırsa, tüm ışın seçimlerinin ağırlıklı toplamı

$$T=H^2$$

olur. Bölüm blokları cinsinden aynı çarpım

$$H=\prod_{u}(u+1)^{c(u)}$$

şeklinde yazılır.

Adım 5: Kapalı Bir Yarı Düzlemde Kalan Seçimleri Çıkar

Bir seçim tam olarak, bütün seçilen yönler bir kapalı yarım çember içinde kaldığında başarısız olur. Bu kötü seçimleri, ilk seçilmiş sınır yönüne göre sayarız.

Ağırlığı \(w_i\) olan bir yönlü sınır ışınını sabitleyelim. Bu ışın üzerinde mutlaka bir nokta seçilmelidir; bunun için \(w_i\) seçenek vardır. Diğer her zıt yön çifti, izleyen açık yarım çember içinde tam bir ışın verdiği için \((1+w_j)\) katsayısı getirir. Karşı sınır ışını seçilebilir ya da boş bırakılabilir; onun katkısı da \((1+w_i)\) olur. Böylece bu sınır yönüne ankrajlanan sayı

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH$$

olur. Bunu her zıt çiftin iki yönü üzerinde toplarsak

$$2H\sum_{i=1}^{m}w_i$$

elde ederiz. Ancak aynı zıt çiftin iki ışınından da birer nokta seçilen altküme iki kez sayılmış olur; biri ilk sınır, diğeri ikinci sınır olarak görünür. Bu çift sayımı düzeltmek için

$$\sum_{i=1}^{m} w_i^2$$

çıkarılır. Boş seçimi de ekleyince

$$B=1+2HA_1-A_2,$$

burada

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

Sonuç olarak

$$P(n)=T-B=H^2-1-2HA_1+A_2$$

olur.

Adım 6: Bölüm Bloklarıyla Son Formül

Aynı bloktaki tüm çiftlerin ağırlığı \(u\) olduğundan, iki moment

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2$$

şeklini alır. Böylece uygulamalarda kullanılan tam ifade

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

Çözümlü Örnek: \(n=2\)

\(n=2\) için yalnızca iki kabuk seviyesi vardır:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

\(k=1\) kabuğu

$$4\varphi(1)=4$$

adet ağırlığı \(2\) olan zıt yön çifti, \(k=2\) kabuğu ise

$$4\varphi(2)=4$$

adet ağırlığı \(1\) olan zıt yön çifti verir. O halde

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

Buradan

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531$$

çıkar ve bu kontrol değeriyle tam uyumludur.

Kod Nasıl Çalışır

C++, Python ve Java uygulamaları aynı matematiksel hattı izler. Önce \(n^{2/3}\) mertebesinde bir eşik seçilir, sonra \(\varphi(1),\dots,\varphi(B)\) bir elek ile hesaplanır ve \(x\le B\) için \(\Phi(x)\) ön-ek değerleri saklanır.

Daha büyük argümanlar için

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2}$$

özdeşliğine dayanan, belleklemeli ve bölme-bloklu bir özyineleme kullanılır. Buradan

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right)$$

elde edilir; eşit bölüm değerleri tek bir aralık halinde gruplanır. Bu sayede blok sınırlarında \(\Phi(x)\) hızlı sorgulanır.

Ardından \(\lfloor n/k\rfloor\) sabit kalan bloklar dolaşılır, iki toplam-totient sorgusundan \(c(u)\) bulunur ve bunlarla \(H\), \(A_1\) ve \(A_2\) güncellenir. Üstel ifadelerde blok çoklukları tam olarak korunur; doğrusal ve ikinci dereceden momentler ise doğrudan \(M\) modunda toplanır.

C++ ve Java sürümleri blokları bağımsız parçalara bölüp kısmi çarpım ve toplamları paralel hesaplayabilir. Python sürümü aynı indirgemeyi tek geçişte seri olarak yapar.

Karmaşıklık Analizi

\(B\) elek eşiği olsun ve büyüklüğü \(n^{2/3}\) mertebesinde seçilsin. Totient tablosunu ve küçük ön-ekleri kurmak \(O(B)\) zaman ve \(O(B)\) bellek ister. Belleklemeli toplam-totient aşaması, tamsayı bölme bloklarını kullandığı için yalnızca farklı bölüm değerlerini ziyaret eder; bu da tek bir ana sorgu için standart \(O(n^{2/3})\)-tipi yaklaşımdır. Son blok taraması yalnızca \(O(\sqrt n)\) ek aritmetik getirir. Pratikte baskın maliyet toplam-totient aşamasıdır; çok iş parçacıklı sürümlerde paralel katlama duvar saati süresini düşürür ama asimptotik sınırı değiştirmez.

Dipnotlar ve Kaynaklar

  1. Problem sayfası: https://projecteuler.net/problem=465
  2. Euler totient fonksiyonu: Wikipedia - Euler phi fonksiyonu
  3. Toplam totient fonksiyonu: MathWorld - Totient Summatory Function
  4. Görünür kafes noktaları ve Farey türü sayım: Wikipedia - Farey dizisi
  5. Dışbükey zarf ve yarı düzlem ölçütü: Wikipedia - Dışbükey kabuk

Resumen del Problema

Definimos

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

Un polígono polar se obtiene eligiendo puntos de la red dentro de \(\mathcal{L}_n\), permitiendo como mucho un vértice en cada semirrecta dirigida que sale del origen, y ordenando los vértices elegidos por ángulo polar creciente. La condición geométrica usada por las implementaciones es que el origen debe pertenecer a la envolvente convexa de los vértices elegidos. De manera equivalente, las direcciones elegidas no pueden quedar contenidas por completo en un único semiplano cerrado que pase por el origen.

La tarea consiste en calcular el número \(P(n)\) de tales polígonos módulo

$$M=10^9+7.$$

Los valores de comprobación incluidos en las implementaciones son

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

Enfoque Matemático

La simplificación principal consiste en olvidar primero las coordenadas exactas y clasificar únicamente por dirección. Cada semirrecta primitiva dirigida lleva un cierto número de puntos de la red, y solo ese número interviene en el conteo.

Paso 1: Sustituir puntos de la red por rayos ponderados

Cada punto no nulo de la red se escribe de forma única como

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

donde \((a,b)\) describe una dirección primitiva. Definimos su nivel como

$$k=\max(|a|,|b|).$$

Los puntos de esa semirrecta dirigida que permanecen dentro de \(\mathcal{L}_n\) son exactamente aquellos con

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor.$$

Por tanto, la semirrecta aporta

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

puntos disponibles. Un polígono válido puede usar como máximo un vértice en una semirrecta dirigida fija, porque dos puntos sobre la misma semirrecta son colineales con el origen y el más cercano no puede ser un vértice extremo. Así, una semirrecta dirigida aporta exactamente

$$1+w(k)$$

opciones: no elegir nada o elegir uno de sus \(w(k)\) puntos.

Paso 2: Contar direcciones primitivas en una capa cuadrada

Fijemos \(k\ge 1\). Las direcciones primitivas de nivel \(k\) son precisamente los puntos visibles de la red sobre la frontera del cuadrado \(\max(|x|,|y|)=k\).

En un octante hay \(\varphi(k)\) direcciones de este tipo, de modo que por simetría aparecen

$$8\varphi(k)$$

semirrectas primitivas dirigidas en total, o lo que es lo mismo,

$$4\varphi(k)$$

pares de direcciones opuestas. Todos los pares de la capa \(k\) tienen el mismo peso \(w(k)=\lfloor n/k\rfloor\).

Paso 3: Agrupar capas con el mismo peso

El peso depende solo del cociente \(\lfloor n/k\rfloor\), no de la dirección concreta. Si ese cociente es constante e igual a \(u\) para todos los \(k\) de un intervalo \([a,b]\), entonces el número de pares opuestos con peso \(u\) es

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

Introducimos la función sumatoria del totiente

$$\Phi(x)=\sum_{m\le x}\varphi(m).$$

Con ella, cada bloque de cociente aporta

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

Ese es el motivo por el que el problema se reduce a solo \(O(\sqrt n)\) bloques distintos: los valores de \(\lfloor n/k\rfloor\) se repiten en intervalos largos.

Paso 4: Contar todas las selecciones antes de imponer la condición del polígono

Sean \(w_1,\dots,w_m\) los pesos de los pares de direcciones opuestas. Para un solo par existen cuatro tipos de elección:

Para un solo par hay cuatro posibilidades: no elegir nada, elegir un punto en el primer rayo, elegir un punto en el rayo opuesto o elegir un punto en cada rayo.

Por lo tanto, la contribución de un par es

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

Si definimos

$$H=\prod_{i=1}^{m}(1+w_i),$$

entonces el conteo ponderado total de todas las selecciones de rayos es

$$T=H^2.$$

En términos de bloques de cociente, el mismo producto se escribe como

$$H=\prod_{u}(u+1)^{c(u)}.$$

Paso 5: Restar las selecciones contenidas en un semiplano cerrado

Una selección falla exactamente cuando todas las direcciones elegidas quedan dentro de un semicírculo cerrado. Para contarlas, fijamos la primera dirección de frontera seleccionada.

Tomemos un rayo de frontera dirigido con peso \(w_i\). Hay que escoger un punto sobre él, así que aporta \(w_i\) posibilidades. Cada uno de los demás pares aporta exactamente una dirección dentro del semicírculo abierto que sigue a esa frontera y, por tanto, contribuye con un factor \(1+w_j\). El rayo opuesto de frontera puede elegirse o no elegirse, lo cual aporta otro factor \(1+w_i\). Por consiguiente, el conteo anclado en esa frontera dirigida es

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

Al sumar sobre las dos orientaciones de cada par opuesto obtenemos

$$2H\sum_{i=1}^{m}w_i.$$

Sin embargo, el subconjunto formado por un punto en ambos rayos del mismo par opuesto queda contado dos veces, una desde cada orientación de frontera. La corrección total de esa doble cuenta es

$$\sum_{i=1}^{m} w_i^2.$$

Incluyendo el subconjunto vacío se llega a

$$B=1+2HA_1-A_2,$$

donde

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

Así pues,

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

Paso 6: Fórmula final por bloques de cociente

Como todos los pares de un mismo bloque tienen peso \(u\), los dos momentos se convierten en

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

Al sustituir se obtiene exactamente la expresión usada por las implementaciones:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

Ejemplo resuelto: \(n=2\)

Para \(n=2\) solo aparecen dos niveles de capa:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

La capa \(k=1\) aporta

$$4\varphi(1)=4$$

pares opuestos de peso \(2\), mientras que la capa \(k=2\) aporta

$$4\varphi(2)=4$$

pares opuestos de peso \(1\). Entonces

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

Por lo tanto,

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

en acuerdo exacto con el valor de comprobación.

Cómo Funciona el Código

Las implementaciones en C++, Python y Java siguen la misma tubería matemática. Primero eligen un umbral del orden de \(n^{2/3}\), calculan \(\varphi(1),\dots,\varphi(B)\) con una criba y guardan los prefijos de \(\Phi(x)\) para todo \(x\le B\).

Para valores mayores usan una recursión memoizada por bloques de división basada en la identidad

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2},$$

que conduce a

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

agrupando en un mismo intervalo los índices con igual cociente. Así se obtienen rápidamente los valores de \(\Phi(x)\) en los extremos de todos los bloques.

Después se enumeran los bloques donde \(\lfloor n/k\rfloor\) es constante, se calcula la multiplicidad \(c(u)\) con dos consultas a la sumatoria del totiente y se actualizan el producto \(H\) y los momentos \(A_1\) y \(A_2\). Las multiplicidades se conservan exactamente para la exponenciación modular, mientras que los momentos aditivos se reducen módulo \(M\).

Las versiones de C++ y Java pueden repartir los bloques entre varios trabajadores y combinar al final productos y sumas parciales. La versión de Python realiza la misma reducción de manera secuencial.

Análisis de Complejidad

Sea \(B\) el corte de la criba, elegido del orden de \(n^{2/3}\). Construir la tabla de totientes y los prefijos pequeños cuesta \(O(B)\) tiempo y \(O(B)\) memoria. La etapa memoizada de la sumatoria del totiente usa bloques de división entera, de modo que solo explora los valores distintos del cociente; esa es la estrategia estándar de tipo \(O(n^{2/3})\) para una consulta principal. El recorrido final de bloques añade solo \(O(\sqrt n)\) operaciones aritméticas. En la práctica domina la fase de totientes, y el paralelismo en C++ y Java mejora el tiempo de pared sin cambiar el orden asintótico.

Notas y Referencias

  1. Página del problema: https://projecteuler.net/problem=465
  2. Función phi de Euler: Wikipedia - Función phi de Euler
  3. Función sumatoria del totiente: MathWorld - Totient Summatory Function
  4. Puntos visibles de la red y conteo tipo Farey: Wikipedia - Sucesión de Farey
  5. Envolvente convexa y criterio de semiplano: Wikipedia - Cierre convexo

问题概述

定义

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

所谓极角多边形,可以理解为从 \(\mathcal{L}_n\) 中挑选一些格点,并要求从原点出发的每一条有向射线上至多选取一个顶点,然后把这些顶点按极角从小到大连接起来。实现采用的几何判据是:原点必须落在所选顶点的凸包内。等价地说,所有被选中的方向不能全部落在某一个经过原点的闭半平面里。

因此问题就是计算这类多边形的个数 \(P(n)\),结果按

$$M=10^9+7$$

取模。

实现中还写入了若干校验值:

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

数学方法

关键化简在于先不看具体坐标,而只看方向。每一条原始有向射线上可选的格点个数是有限的,而计数最终只依赖这个个数。

步骤 1:把格点改写成带权射线

每个非零格点都能唯一写成

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

其中 \((a,b)\) 给出一条原始方向。定义该方向的层级为

$$k=\max(|a|,|b|).$$

这条有向射线上落在 \(\mathcal{L}_n\) 内的点,正好对应于

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor.$$

因此,这条射线一共携带

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

个可选格点。一个有效多边形在同一条有向射线上最多只能放一个顶点,因为同射线上的两个点与原点共线,较近的那个不可能成为凸包上的极点。所以,一条有向射线恰好贡献

$$1+w(k)$$

种选择:要么不选,要么从这 \(w(k)\) 个点里选一个。

步骤 2:统计同一方形壳层上的原始方向数

固定 \(k\ge 1\)。层级为 \(k\) 的原始方向,正是边界 \(\max(|x|,|y|)=k\) 上那些与原点可见的格点方向。

在一个八分区里,这样的方向有 \(\varphi(k)\) 个,所以利用对称性,整条方形边界上一共有

$$8\varphi(k)$$

条有向原始射线,也就是

$$4\varphi(k)$$

对相反方向。该壳层内的每一对相反方向都具有相同权重

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor.$$

步骤 3:按相同权重把壳层合并成整除分块

权重只依赖于商 \(\lfloor n/k\rfloor\),与具体方向无关。若对区间 \([a,b]\) 内的所有 \(k\),这个商都等于同一个值 \(u\),那么权重为 \(u\) 的相反方向对数量就是

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

记 totient 的前缀和为

$$\Phi(x)=\sum_{m\le x}\varphi(m).$$

于是分块贡献可以写成

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

这一步说明了为什么问题只剩下 \(O(\sqrt n)\) 个不同块:\(\lfloor n/k\rfloor\) 的值会在较长区间上重复出现。

步骤 4:先统计所有可能的选择

设全部相反方向对的权重为 \(w_1,\dots,w_m\)。对第 \(i\) 对来说,一共有四种情形:

对一对相反方向来说,一共有四种情况:都不选,只选第一条射线上的一点,只选相反射线上的一点,或者两条射线各选一点。

因此这一对的总贡献是

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

定义

$$H=\prod_{i=1}^{m}(1+w_i),$$

那么全部选择的加权总数就是

$$T=H^2.$$

按整除分块改写后,有

$$H=\prod_{u}(u+1)^{c(u)}.$$

步骤 5:减去所有落在闭半平面中的坏选择

一个选择失败,当且仅当所有被选方向都落在某个闭半圆内。对这类坏选择,可以按“第一个被选中的边界方向”来计数。

固定一条边界有向射线,其权重为 \(w_i\)。这条边界上必须选一个点,因此有 \(w_i\) 种办法。对于其余每一对相反方向,在这条边界之后的开半圆里恰好只有一个方向可用,所以它们各自贡献因子 \(1+w_j\)。与之相对的另一条边界射线可以选也可以不选,因此再贡献一个 \(1+w_i\)。于是,以这条有向边界为锚点的计数为

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

对每一对相反方向的两个朝向都求和,得到

$$2H\sum_{i=1}^{m}w_i.$$

不过,如果只在同一对相反射线上各选一个点,这个集合会被两次统计,因为它既可以从一个边界方向出发计数,也可以从另一个边界方向出发计数。需要减去的重复量正是

$$\sum_{i=1}^{m} w_i^2.$$

再把空集算进去,就得到

$$B=1+2HA_1-A_2,$$

其中

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

所以最终有

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

步骤 6:写成按商值分块的最终公式

因为同一块中的所有相反方向对都拥有相同权重 \(u\),所以上面的两个矩可以写成

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

代回去以后,就得到实现真正计算的闭式:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

例子:\(n=2\)

当 \(n=2\) 时,只有两层:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

层 \(k=1\) 提供

$$4\varphi(1)=4$$

对权重为 \(2\) 的相反方向;层 \(k=2\) 提供

$$4\varphi(2)=4$$

对权重为 \(1\) 的相反方向。于是

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

因此

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

与校验值完全一致。

代码如何工作

C++、Python 和 Java 实现遵循同一条数学流程。首先选取一个大约为 \(n^{2/3}\) 的阈值,用筛法求出 \(\varphi(1),\dots,\varphi(B)\),并保存所有 \(x\le B\) 的 \(\Phi(x)\) 前缀值。

对更大的参数,程序使用基于下列恒等式的记忆化整除分块递推:

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2}.$$

由此推出

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

再把相同商值的项合并成一个区间。这样就能快速得到每个整除分块边界上的 \(\Phi(x)\)。

接着,程序枚举所有 \(\lfloor n/k\rfloor\) 保持常数的区间,用两次前缀 totient 查询得到块重数 \(c(u)\),再更新乘积 \(H\) 以及两个矩 \(A_1,A_2\)。块重数在模幂时按精确值使用,而两个加法型矩则直接按 \(M\) 取模累加。

C++ 和 Java 版本还能把这些分块拆给多个工作线程,分别算出局部乘积与局部矩后再合并。Python 版本执行的是同样的归约,只是串行完成。

复杂度分析

记筛法阈值为 \(B\),其规模约为 \(n^{2/3}\)。建立 totient 表和小范围前缀和需要 \(O(B)\) 时间与 \(O(B)\) 空间。记忆化的 totient 前缀求值使用整除分块,因此只访问不同的商值;在标准分析下,这对应于单次主查询的 \(O(n^{2/3})\) 级别方法。最后的分块遍历只增加 \(O(\sqrt n)\) 的算术量。实际运行中主导成本仍是 totient 阶段,而并行化只改善多线程版本的实际耗时,不改变渐近复杂度。

注释与参考资料

  1. 题目页面:https://projecteuler.net/problem=465
  2. Euler totient 函数:Wikipedia - 欧拉函数
  3. Totient 前缀和函数:MathWorld - Totient Summatory Function
  4. 可见格点与 Farey 型计数:Wikipedia - 费尔序列
  5. 凸包与半平面判据:Wikipedia - 凸包

Краткое описание задачи

Обозначим

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

Полярный многоугольник получается так: выбираются точки решетки из \(\mathcal{L}_n\), на каждом направленном луче из начала координат допускается не более одной вершины, а затем выбранные точки упорядочиваются по возрастанию полярного угла. Геометрический критерий, который используют реализации, таков: начало координат должно лежать внутри выпуклой оболочки выбранных вершин. Эквивалентно, все выбранные направления не могут целиком помещаться в одну замкнутую полуплоскость, проходящую через начало координат.

Нужно вычислить число таких многоугольников \(P(n)\) по модулю

$$M=10^9+7.$$

В реализациях также зашиты контрольные значения

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

Математический подход

Главное упрощение состоит в том, чтобы сначала забыть о точных координатах и классифицировать вершины только по направлениям. Каждый примитивный направленный луч несет некоторое число решетчатых точек, и именно это число определяет подсчет.

Шаг 1: заменить точки решетки взвешенными лучами

Любая ненулевая точка решетки единственным образом записывается в виде

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

где \((a,b)\) задает примитивное направление. Пусть уровень этого направления равен

$$k=\max(|a|,|b|).$$

Точки на данном направленном луче, остающиеся внутри \(\mathcal{L}_n\), соответствуют точно тем \(t\), для которых

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor.$$

Значит, луч содержит

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor$$

доступных точек. Корректный многоугольник может использовать не более одной вершины на фиксированном направленном луче, потому что две точки на одном луче коллинеарны с началом координат, а более близкая из них не может быть крайней вершиной. Следовательно, один направленный луч дает ровно

$$1+w(k)$$

вариантов: ничего не выбрать либо выбрать одну из \(w(k)\) точек.

Шаг 2: посчитать примитивные направления на одном квадратном слое

Зафиксируем \(k\ge 1\). Примитивные направления уровня \(k\) - это в точности видимые точки решетки на границе квадрата \(\max(|x|,|y|)=k\).

В одном октанте таких направлений \(\varphi(k)\), поэтому по симметрии всего имеется

$$8\varphi(k)$$

направленных примитивных лучей, то есть

$$4\varphi(k)$$

пар противоположных направлений. Все пары на уровне \(k\) имеют один и тот же вес \(w(k)=\lfloor n/k\rfloor\).

Шаг 3: объединить слои с одинаковым весом

Вес зависит только от частного \(\lfloor n/k\rfloor\), а не от конкретного направления. Если это частное постоянно и равно \(u\) для всех \(k\) из интервала \([a,b]\), то число противоположных пар веса \(u\) равно

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

Введем сумматор функции Эйлера

$$\Phi(x)=\sum_{m\le x}\varphi(m).$$

Тогда вклад блока записывается так:

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

Именно поэтому задача сжимается до \(O(\sqrt n)\) различных блоков: значения \(\lfloor n/k\rfloor\) повторяются на длинных промежутках.

Шаг 4: посчитать все выборы до наложения геометрического условия

Пусть веса противоположных пар равны \(w_1,\dots,w_m\). Для одной пары возможны четыре типа выбора:

Для одной пары есть четыре возможности: ничего не выбрать, выбрать одну точку на первом луче, выбрать одну точку на противоположном луче или выбрать по одной точке на обоих лучах.

Поэтому вклад одной пары равен

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

Если обозначить

$$H=\prod_{i=1}^{m}(1+w_i),$$

то общее взвешенное число всех выборов лучей равно

$$T=H^2.$$

После группировки по блокам частных тот же множитель принимает вид

$$H=\prod_{u}(u+1)^{c(u)}.$$

Шаг 5: вычесть выборы, лежащие в замкнутой полуплоскости

Выбор плох тогда и только тогда, когда все выбранные направления лежат в некотором замкнутом полуокружности. Посчитаем такие выборы по первой выбранной граничной направляющей.

Зафиксируем один направленный граничный луч веса \(w_i\). На нем обязательно нужно выбрать точку, что дает \(w_i\) вариантов. Каждая из остальных противоположных пар дает ровно одно направление внутри следующей открытой полуокружности, а значит вносит множитель \(1+w_j\). Сам противоположный граничный луч можно выбрать или пропустить, так что он дает еще множитель \(1+w_i\). Следовательно, число выборов, заякоренных в этой граничной направляющей, равно

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

Сумма по двум ориентациям каждой противоположной пары дает

$$2H\sum_{i=1}^{m}w_i.$$

Однако подмножество, состоящее из одной точки на каждом луче одной и той же противоположной пары, посчитано дважды: один раз от одной границы и один раз от другой. Полная поправка на двойной счет равна

$$\sum_{i=1}^{m} w_i^2.$$

Добавляя пустой выбор, получаем

$$B=1+2HA_1-A_2,$$

где

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

Отсюда

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

Шаг 6: окончательная формула по блокам частных

Поскольку все пары внутри одного блока имеют одинаковый вес \(u\), два момента записываются как

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

Подстановка дает точную формулу, реализованную в программах:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

Разобранный пример: \(n=2\)

При \(n=2\) существуют только два уровня слоев:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

Слой \(k=1\) дает

$$4\varphi(1)=4$$

противоположных пары веса \(2\), а слой \(k=2\) дает

$$4\varphi(2)=4$$

противоположных пары веса \(1\). Поэтому

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

Следовательно,

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

что совпадает с контрольным значением.

Как работает код

Реализации на C++, Python и Java следуют одной и той же математической схеме. Сначала выбирается порог порядка \(n^{2/3}\), затем с помощью решета вычисляются \(\varphi(1),\dots,\varphi(B)\), а для всех \(x\le B\) сохраняются префиксные значения \(\Phi(x)\).

Для больших аргументов используется мемоизированная рекурсия по блокам целочисленного деления, основанная на тождестве

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2}.$$

Из него выводится

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

а одинаковые значения частного объединяются в один интервал. Это позволяет быстро получать \(\Phi(x)\) на границах всех блоков.

Затем перечисляются все интервалы, на которых \(\lfloor n/k\rfloor\) постоянно, по двум запросам к сумматору функции Эйлера вычисляется кратность \(c(u)\), и обновляются произведение \(H\) и моменты \(A_1\) и \(A_2\). Кратности блоков хранятся точно для модульного возведения в степень, а аддитивные моменты сразу суммируются по модулю \(M\).

В версиях на C++ и Java блоки могут делиться между несколькими потоками, после чего частичные произведения и суммы сливаются в финальный ответ. Версия на Python выполняет ту же редукцию последовательно.

Анализ сложности

Пусть \(B\) - порог решета, выбранный порядка \(n^{2/3}\). Построение таблицы тотиентов и малых префиксов требует \(O(B)\) времени и \(O(B)\) памяти. Мемоизированный сумматор функции Эйлера использует блоки целочисленного деления и потому посещает только различные значения частного; это стандартный метод типа \(O(n^{2/3})\) для одного верхнего запроса. Финальный проход по блокам добавляет лишь \(O(\sqrt n)\) арифметики. На практике доминирует этап вычисления сумматора тотиента, а распараллеливание улучшает лишь реальное время работы, не меняя асимптотики.

Примечания и ссылки

  1. Страница задачи: https://projecteuler.net/problem=465
  2. Функция Эйлера: Wikipedia - Функция Эйлера
  3. Сумматор функции Эйлера: MathWorld - Totient Summatory Function
  4. Видимые точки решетки и счет типа Farey: Wikipedia - Ряд Фарея
  5. Выпуклая оболочка и критерий полуплоскости: Wikipedia - Выпуклая оболочка

ملخص المسألة

لنعرّف المجموعة

$$\mathcal{L}_n=\left\{(x,y)\in\mathbb{Z}^2\setminus\{(0,0)\}: \max(|x|,|y|)\le n\right\}.$$

يتكوّن المضلع القطبي من اختيار نقاط شبكية من \(\mathcal{L}_n\)، مع السماح بحد أقصى رأس واحد على كل شعاع موجّه صادر من الأصل، ثم ترتيب الرؤوس المختارة بحسب الزاوية القطبية تصاعديًا. الشرط الهندسي الذي تعتمد عليه التطبيقات هو أن يقع الأصل داخل الغلاف المحدّب للرؤوس المختارة. وبصورة مكافئة، لا يجوز أن تنحصر جميع الاتجاهات المختارة داخل نصف مستوى مغلق واحد يمر بالأصل.

إذن المطلوب هو حساب عدد هذه المضلعات \(P(n)\) بترديد

$$M=10^9+7.$$

وتستخدم التطبيقات أيضًا قيم تحقق هي

$$P(1)=131,\qquad P(2)=1648531,\qquad P(3)\equiv 461288482 \pmod{M},\qquad P(343)\equiv 937293740 \pmod{M}.$$

المنهج الرياضي

الفكرة الحاسمة هي تجاهل الإحداثيات الدقيقة في البداية، والتصنيف بحسب الاتجاه فقط. فكل شعاع بدائي موجّه يحمل عددًا معينًا من النقاط الشبكية، وهذا العدد وحده هو الذي يدخل في العد.

الخطوة 1: استبدال النقاط الشبكية بأشعة موزونة

كل نقطة شبكية غير صفرية تكتب بصورة وحيدة على الشكل

$$t(a,b),\qquad t\ge 1,\qquad \gcd(a,b)=1,$$

حيث يحدد الزوج \((a,b)\) اتجاهًا بدائيًا. ولنعرف مستوى هذا الاتجاه بـ

$$k=\max(|a|,|b|).$$

النقاط الواقعة على هذا الشعاع الموجّه والتي تبقى داخل \(\mathcal{L}_n\) هي تمامًا تلك التي تحقق

$$1\le t\le \left\lfloor\frac{n}{k}\right\rfloor.$$

ومن ثم فإن عدد النقاط المتاحة على هذا الشعاع يساوي

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor.$$

ولا يمكن لمضلع صحيح أن يستخدم أكثر من رأس واحد على شعاع موجّه ثابت، لأن نقطتين على الشعاع نفسه تكونان على استقامة واحدة مع الأصل، والنقطة الأقرب لا يمكن أن تكون رأسًا متطرفًا في الغلاف المحدّب. لذلك يساهم كل شعاع موجّه بعدد خيارات يساوي

$$1+w(k)$$

أي إما عدم اختيار شيء، أو اختيار نقطة واحدة من بين \(w(k)\) نقطة.

الخطوة 2: عدّ الاتجاهات البدائية على غلاف مربعي واحد

ثبّت \(k\ge 1\). إن الاتجاهات البدائية ذات المستوى \(k\) هي بالضبط النقاط الشبكية المرئية على حدود المربع \(\max(|x|,|y|)=k\).

في كل ثمن دائرة يوجد \(\varphi(k)\) من هذه الاتجاهات، ولذلك نحصل بالتناظر على

$$8\varphi(k)$$

شعاعًا بدائيًا موجّهًا، أي

$$4\varphi(k)$$

أزواجًا من الاتجاهات المتقابلة. وجميع الأزواج في الغلاف ذي المستوى \(k\) تملك الوزن نفسه

$$w(k)=\left\lfloor\frac{n}{k}\right\rfloor.$$

الخطوة 3: تجميع الأغلفة ذات الوزن نفسه

الوزن يعتمد فقط على خارج القسمة \(\lfloor n/k\rfloor\)، لا على الاتجاه ذاته. فإذا كان هذا الخارج ثابتًا ويساوي \(u\) لكل \(k\) في المجال \([a,b]\)، فإن عدد الأزواج المتقابلة ذات الوزن \(u\) هو

$$c(u)=4\sum_{k=a}^{b}\varphi(k).$$

ولنعرّف دالة المجموع التراكمي للتوتينت بـ

$$\Phi(x)=\sum_{m\le x}\varphi(m).$$

حينئذ يصبح إسهام الكتلة

$$c(u)=4\bigl(\Phi(b)-\Phi(a-1)\bigr).$$

وهذا يفسر لماذا ينهار التعقيد إلى \(O(\sqrt n)\) كتل مختلفة فقط: فقيم \(\lfloor n/k\rfloor\) تتكرر على فترات طويلة.

الخطوة 4: عدّ جميع الاختيارات قبل فرض شرط المضلع

لتكن أوزان الأزواج المتقابلة هي \(w_1,\dots,w_m\). بالنسبة إلى زوج واحد فقط توجد أربع حالات:

بالنسبة إلى زوج واحد فهناك أربع إمكانات: عدم اختيار شيء، أو اختيار نقطة على الشعاع الأول فقط، أو اختيار نقطة على الشعاع المقابل فقط، أو اختيار نقطة على كل من الشعاعين.

ولذلك يكون إسهام الزوج الواحد

$$1+w_i+w_i+w_i^2=(1+w_i)^2.$$

إذا عرّفنا

$$H=\prod_{i=1}^{m}(1+w_i),$$

فإن العدد الموزون لجميع اختيارات الأشعة يساوي

$$T=H^2.$$

وبصياغة الكتل نحصل على

$$H=\prod_{u}(u+1)^{c(u)}.$$

الخطوة 5: طرح الاختيارات الواقعة في نصف مستوى مغلق

يكون الاختيار فاسدًا إذا وفقط إذا كانت جميع الاتجاهات المختارة محصورة في نصف دائرة مغلق. ولعدّ هذه الاختيارات نحسبها بحسب أول اتجاه حدّي مختار.

ثبّت شعاعًا حدّيًا موجّهًا وزنه \(w_i\). يجب اختيار نقطة على هذا الشعاع، وهذا يعطي \(w_i\) إمكانات. أما كل زوج متقابل آخر فيعطي اتجاهًا واحدًا فقط داخل نصف الدائرة المفتوح التالي لهذا الحد، ولذلك يساهم بعامل \(1+w_j\). ويمكن اختيار الشعاع الحدّي المقابل أو تركه، فيعطي عاملًا إضافيًا \(1+w_i\). ومن ثم يكون عدد الاختيارات المرتكزة على هذا الحد الموجّه

$$w_i\prod_{j=1}^{m}(1+w_j)=w_iH.$$

وبالجمع على الاتجاهين لكل زوج متقابل نحصل على

$$2H\sum_{i=1}^{m}w_i.$$

لكن المجموعة التي تحتوي على نقطة واحدة على كل من شعاعي الزوج المتقابل نفسه تُعدّ مرتين، مرة من كل حد. لذا يجب طرح مقدار التصحيح

$$\sum_{i=1}^{m} w_i^2.$$

ومع إضافة المجموعة الخالية نحصل على

$$B=1+2HA_1-A_2,$$

حيث

$$A_1=\sum_{i=1}^{m} w_i,\qquad A_2=\sum_{i=1}^{m} w_i^2.$$

إذًا

$$P(n)=T-B=H^2-1-2HA_1+A_2.$$

الخطوة 6: الصيغة النهائية بعد التجميع بالكتل

لأن جميع الأزواج داخل الكتلة الواحدة لها الوزن نفسه \(u\)، فإن العزمين يكتبان على الصورة

$$A_1=\sum_{u} c(u)\,u,\qquad A_2=\sum_{u} c(u)\,u^2.$$

وبالتعويض نحصل على الصيغة المغلقة التي تحسبها التطبيقات مباشرة:

$$\boxed{P(n)\equiv \left(\prod_{u}(u+1)^{c(u)}\right)^2-1-2\left(\prod_{u}(u+1)^{c(u)}\right)\left(\sum_{u} c(u)\,u\right)+\sum_{u} c(u)\,u^2 \pmod{M}.}$$

مثال محلول: \(n=2\)

عند \(n=2\) لا يوجد إلا مستويان من الأغلفة:

$$\left\lfloor\frac{2}{1}\right\rfloor=2,\qquad \left\lfloor\frac{2}{2}\right\rfloor=1.$$

يعطي الغلاف \(k=1\)

$$4\varphi(1)=4$$

أزواجًا متقابلة وزنها \(2\)، ويعطي الغلاف \(k=2\)

$$4\varphi(2)=4$$

أزواجًا متقابلة وزنها \(1\). ومن ثم

$$H=(1+2)^4(1+1)^4=3^4\cdot 2^4=1296,$$

$$A_1=4\cdot 2+4\cdot 1=12,\qquad A_2=4\cdot 2^2+4\cdot 1^2=20.$$

وبالتالي

$$P(2)=1296^2-1-2\cdot1296\cdot12+20=1648531,$$

وهو يطابق قيمة التحقق تمامًا.

كيف تعمل الشيفرة

تتبع تطبيقات C++ وPython وJava السلسلة الرياضية نفسها. في البداية تختار حدًا من رتبة \(n^{2/3}\)، ثم تحسب \(\varphi(1),\dots,\varphi(B)\) باستعمال غربال، وتخزن القيم السابقة لـ \(\Phi(x)\) لكل \(x\le B\).

أما للقيم الأكبر فتستخدم استدعاءً تكراريًا مع التخزين المؤقت قائمًا على تجميع كتل القسمة الصحيحة، انطلاقًا من الهوية

$$\sum_{d=1}^{x}\varphi(d)\left\lfloor\frac{x}{d}\right\rfloor=\frac{x(x+1)}{2}.$$

ومنها ينتج

$$\Phi(x)=\frac{x(x+1)}{2}-\sum_{i=2}^{x}\Phi\!\left(\left\lfloor\frac{x}{i}\right\rfloor\right),$$

مع دمج الحدود التي تعطي خارج قسمة واحدًا في مجال واحد. وهكذا تُحسب قيم \(\Phi(x)\) بسرعة عند حدود كل كتلة.

بعد ذلك تُعدّد الكتل التي يكون فيها \(\lfloor n/k\rfloor\) ثابتًا، ويُستخرج التعدد \(c(u)\) من استعلامين للمجموع التراكمي للتوتينت، ثم يُحدّث الجداء \(H\) والعزمان \(A_1\) و\(A_2\). وتحفظ تعددات الكتل بدقة كاملة عند الأسس في القوة المعيارية، بينما تُجمع العزوم الإضافية مباشرة بترديد \(M\).

ويمكن في نسختي C++ وJava تقسيم الكتل بين عدة خيوط عمل ثم دمج الجداءات والمجاميع الجزئية في النهاية. أما نسخة Python فتنفذ الاختزال نفسه على نحو تسلسلي.

تحليل التعقيد

ليكن \(B\) حد الغربال المختار من رتبة \(n^{2/3}\). بناء جدول التوتينت والقيم السابقة الصغيرة يكلف \(O(B)\) زمنًا و\(O(B)\) ذاكرة. أما مرحلة المجموع التراكمي للتوتينت مع التخزين المؤقت فتستخدم كتل القسمة الصحيحة، ولذلك لا تزور إلا قيم الخارج المختلفة؛ وهذا هو الأسلوب القياسي من النمط \(O(n^{2/3})\) لاستعلام رئيسي واحد. ثم تضيف مرحلة المرور على الكتل \(O(\sqrt n)\) عملية حسابية فقط. عمليًا تظل مرحلة التوتينت هي المسيطرة، بينما يختصر التوازي زمن التنفيذ الفعلي في النسخ متعددة الخيوط من غير أن يغير الرتبة الأسية.

حواشٍ ومراجع

  1. صفحة المسألة: https://projecteuler.net/problem=465
  2. دالة أويلر للتوتينت: Wikipedia - دالة أويلر
  3. دالة مجموع التوتينت: MathWorld - Totient Summatory Function
  4. النقاط الشبكية المرئية والعد من نوع Farey: Wikipedia - متتالية فاراي
  5. الغلاف المحدب ومعيار نصف المستوى: Wikipedia - غلاف محدب