Problem Summary

In Nim Square, a move on one heap of size \(n\) removes any positive square \(k^2\le n\). The position has three heaps, and we must count how many ordered-by-size triples

$$0\le a\le b\le c\le N$$

are losing positions.

Mathematical Approach

1) Single-heap Grundy numbers

For one heap, the legal moves are

$$n\to n-1,\ n-4,\ n-9,\dots$$

for every square not exceeding \(n\). So the Grundy value of a single heap is

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

This is the usual subtract-a-square recurrence.

2) Why three heaps reduce to xor

The full game is the disjoint sum of three impartial subgames. Therefore Sprague-Grundy theory says that the triple \((a,b,c)\) is losing exactly when the nim-sum vanishes:

$$g(a)\oplus g(b)\oplus g(c)=0.$$

So once the array \(g(0),\dots,g(N)\) is known, the entire counting problem becomes a combinatorial xor-counting problem.

3) Small worked example

The first few single-heap values are

$$g(0..4)=(0,1,0,1,2).$$

For instance:

$$g(1)=\operatorname{mex}\{g(0)\}=\operatorname{mex}\{0\}=1,$$

$$g(2)=\operatorname{mex}\{g(1)\}=\operatorname{mex}\{1\}=0,$$

$$g(4)=\operatorname{mex}\{g(3),g(0)\}=\operatorname{mex}\{1,0\}=2.$$

If \(N=4\), the losing triples are

$$\begin{aligned} &(0,0,0),(0,0,2),(0,1,1),(0,1,3),(0,2,2),(0,3,3),\\ &(0,4,4),(1,1,2),(1,2,3),(2,2,2),(2,3,3),(2,4,4), \end{aligned}$$

so the count is \(12\).

4) The naive count is cubic

A direct scan over all triples \((a,b,c)\) would take

$$O(N^3)$$

time, which is impossible for \(N=100000\). The code instead reorganizes the count by fixing the middle heap \(b\).

5) Prefix/suffix counting for a fixed middle heap

Fix \(b\). Then:

Left side: \(a\) may range over \(0\le a\le b\).

Right side: \(c\) may range over \(b\le c\le N\).

Let

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

For a fixed Grundy value \(x=g(a)\), the losing condition

$$x\oplus g(b)\oplus g(c)=0$$

forces

$$g(c)=x\oplus g(b).$$

Hence the number of losing triples with this middle value \(b\) is

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

Summing \(\text{add}_b\) over all \(b\) gives the final answer.

6) Why this automatically respects \(a\le b\le c\)

The implementation maintains the arrays in a very specific order:

Before counting for \(b\): it adds \(g(b)\) to the prefix array.

During counting: prefix therefore represents \(a\in[0,b]\), while suffix still represents \(c\in[b,N]\).

After counting: it removes \(g(b)\) from suffix, so the next step uses \(c\ge b+1\).

This means the ordering constraint is built directly into the data structure update order; no extra combinatorial correction is needed.

How the Code Works

1) Build Grundy table. build_grundy(limit) computes the subtract-a-square Grundy values using a timestamp-based mex array.

2) Determine Grundy alphabet size. The code records

$$\max g(n)$$

so it knows how large the prefix and suffix frequency arrays must be.

3) Initialize suffix. Initially all heaps \(0,\dots,N\) are available on the right, so suffix counts every Grundy value in the full range.

4) Sweep the middle index. For each \(b\), the code updates prefix, applies the xor-count formula above, then removes \(b\) from suffix.

5) Checkpoints. The C++ version verifies

$$\texttt{solve(29)}=1160$$

and also checks that \(\texttt{solve(40)}\) matches a literal brute-force triple scan.

6) Final Project Euler value. For the required bound \(N=100000\), the implementation returns

$$2586528661783.$$

Complexity Analysis

Computing the Grundy table costs about \(O(N\sqrt N)\), because each \(g(n)\) examines all squares up to \(n\). The counting sweep costs \(O(N\cdot G)\), where \(G\) is the number of distinct Grundy values encountered, which stays small in practice. Memory usage is \(O(N+G)\).

Further Reading

  1. Problem page: https://projecteuler.net/problem=310
  2. Sprague-Grundy theorem: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Subtract-a-square game: https://en.wikipedia.org/wiki/Subtract_a_square

Problemzusammenfassung

Bei Nim Square darf man aus einem Haufen der Größe \(n\) jede positive Quadratzahl \(k^2\le n\) entfernen. Gesucht ist die Anzahl verlorener Tripel

$$0\le a\le b\le c\le N.$$

Mathematischer Ansatz

1) Grundy-Zahlen für einen einzelnen Haufen

Für einen Haufen sind die Züge

$$n\to n-1,\ n-4,\ n-9,\dots$$

für alle Quadrate \(\le n\). Daher gilt

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

2) Warum drei Haufen per xor beschrieben werden

Das Gesamtsystem ist die disjunkte Summe dreier impartial games. Also ist \((a,b,c)\) genau dann verloren, wenn

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) Kleines Beispiel

Die ersten Werte lauten

$$g(0..4)=(0,1,0,1,2).$$

Für \(N=4\) ergeben sich genau 12 verlorene Tripel, etwa \((0,0,0)\), \((0,1,1)\), \((1,2,3)\), \((2,2,2)\) usw. Dieses kleine Beispiel zeigt, dass die Aufgabe nicht in der Spieltheorie selbst, sondern im effizienten Zählen aller xor-Null-Tripel liegt.

4) Die naive Zählung wäre kubisch

Ein direkter Dreifach-Loop über alle \((a,b,c)\) hätte Aufwand

$$O(N^3),$$

also völlig unpraktisch für \(N=100000\).

5) Präfix/Suffix-Idee für festes \(b\)

Fixiere den mittleren Haufen \(b\). Dann gilt

$$0\le a\le b,\qquad b\le c\le N.$$

Definiere Häufigkeiten

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

Aus

$$g(a)\oplus g(b)\oplus g(c)=0$$

folgt für \(x=g(a)\):

$$g(c)=x\oplus g(b).$$

Also ist der Beitrag des festen Mittels \(b\)

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

6) Warum damit automatisch \(a\le b\le c\) gilt

Vor dem Zählen für \(b\) wird \(g(b)\) bereits in das Präfix aufgenommen. Während des Zählens enthält das Präfix also genau die Werte für \(a\in[0,b]\), das Suffix aber noch die Werte für \(c\in[b,N]\). Erst danach wird \(b\) aus dem Suffix entfernt. Genau diese Update-Reihenfolge erzwingt die Ordnung ohne Zusatzkorrektur.

Wie der Code arbeitet

1) Grundy-Tabelle. build_grundy(limit) berechnet alle Werte mit einem zeitgestempelten mex-Array.

2) Alphabetgröße. Danach bestimmt der Code das Maximum der auftretenden Grundy-Werte, um die Häufigkeitsarrays zu dimensionieren.

3) Suffix initialisieren. Anfangs enthält das rechte Intervall alle Positionen \(0,\dots,N\).

4) Sweep über \(b\). Für jedes \(b\) wird erst das Präfix erweitert, dann \(\text{add}_b\) berechnet, dann das Suffix angepasst.

5) Checkpoints. Verifiziert werden

$$\texttt{solve(29)}=1160$$

und ein vollständiger brute-force-Abgleich bei \(N=40\).

6) Endwert. Für \(N=100000\) ergibt sich

$$2586528661783.$$

Komplexitätsanalyse

Die Grundy-Vorberechnung kostet etwa \(O(N\sqrt N)\), weil für jedes \(n\) alle Quadrate bis \(n\) betrachtet werden. Die Zählphase kostet \(O(N\cdot G)\), wobei \(G\) die Zahl verschiedener Grundy-Werte ist und in der Praxis klein bleibt. Speicher: \(O(N+G)\).

Weiterführende Hinweise

  1. Problem: https://projecteuler.net/problem=310
  2. Sprague-Grundy-Satz: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Subtract-a-square: https://en.wikipedia.org/wiki/Subtract_a_square

Problem Özeti

Nim Square oyununda bir yığından, onu aşmayan herhangi bir pozitif kare \(k^2\) çıkarılabilir. Amaç,

$$0\le a\le b\le c\le N$$

koşulunu sağlayan üçlülerden kaç tanesinin kaybeden konum olduğunu saymaktır.

Matematiksel Yaklaşım

1) Tek yığın için Grundy değerleri

Bir yığındaki hamleler

$$n\to n-1,\ n-4,\ n-9,\dots$$

şeklindedir. Bu yüzden

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}$$

bağıntısı elde edilir.

2) Neden üç yığın xor ile biter?

Üç yığın oyunu, üç bağımsız impartial game'in ayrık toplamıdır. Dolayısıyla bir üçlü tam ve ancak şu durumda kaybedendir:

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) Küçük çalışan örnek

İlk tek-yığın değerleri

$$g(0..4)=(0,1,0,1,2)$$

şeklindedir. Örneğin

$$g(1)=\operatorname{mex}\{0\}=1,\qquad g(2)=\operatorname{mex}\{1\}=0,\qquad g(4)=\operatorname{mex}\{1,0\}=2.$$

\(N=4\) için toplam 12 kaybeden üçlü vardır; bunlar arasında \((0,0,0)\), \((0,1,1)\), \((1,2,3)\), \((2,2,2)\) gibi örnekler bulunur.

4) Naif sayım neden çalışmaz?

Tüm \((a,b,c)\) üçlülerini doğrudan dolaşmak

$$O(N^3)$$

zaman gerektirir. \(N=100000\) için bu imkansızdır.

5) Sabit orta yığın \(b\) için prefix/suffix sayımı

Orta yığın \(b\) sabitlensin. O zaman

$$0\le a\le b,\qquad b\le c\le N.$$

Şu frekans dizilerini tanımlayalım:

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

Kaybetme koşulu

$$g(a)\oplus g(b)\oplus g(c)=0$$

olduğundan, \(x=g(a)\) için gerekli sağ taraftaki Grundy değeri

$$g(c)=x\oplus g(b)$$

olur. Böylece sabit \(b\)'nin katkısı

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)]$$

şeklindedir.

6) Bu formül \(a\le b\le c\) sırasını nasıl doğru sayıyor?

Kod önce \(g(b)\)'yi prefix tarafına ekler. Bu yüzden sayım anında prefix gerçekten \(a\in[0,b]\) aralığını temsil eder. Aynı anda suffix hâlâ \(c\in[b,N]\) aralığını temsil eder. Sayım bittikten sonra \(b\), suffix'ten çıkarılır. Yani eşitsizlikler, veri yapılarının güncellenme sırasına gömülüdür; ayrıca düzeltme faktörü gerekmez.

Kodun Çalışma Mantığı

1) Grundy tablosu. build_grundy(limit), timestamp tabanlı bir mex dizisiyle tüm tek-yığın değerlerini üretir.

2) Grundy alfabesi boyutu. Sonra kod, ortaya çıkan maksimum Grundy değerini bulup frekans dizilerinin boyutunu belirler.

3) Suffix başlangıcı. Başta sağ taraf \(0,\dots,N\) aralığının tamamını içerir.

4) \(b\) üzerinden sweep. Her \(b\) için önce prefix güncellenir, sonra \(\text{add}_b\) toplanır, sonra suffix ayarlanır.

5) Checkpoint'ler. C++ sürümü

$$\texttt{solve(29)}=1160$$

değerini ve \(N=40\) için tam brute-force karşılaştırmasını doğrular.

6) Nihai Project Euler cevabı. \(N=100000\) için sonuç

$$2586528661783$$

olarak döner.

Karmaşıklık Analizi

Grundy tablosunu kurmak yaklaşık \(O(N\sqrt N)\) zaman alır; çünkü her \(n\) için tüm kare hamleleri kontrol edilir. Sayım sweep'i ise \(O(N\cdot G)\) sürer; burada \(G\), görülen farklı Grundy değeri sayısıdır ve pratikte küçüktür. Bellek kullanımı \(O(N+G)\)'dir.

İleri Okuma

  1. Problem metni: https://projecteuler.net/problem=310
  2. Sprague-Grundy teoremi: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Subtract-a-square oyunu: https://en.wikipedia.org/wiki/Subtract_a_square

Resumen del Problema

En Nim Square, desde un montón de tamaño \(n\) se puede quitar cualquier cuadrado perfecto positivo \(k^2\le n\). Debemos contar cuántas ternas

$$0\le a\le b\le c\le N$$

son posiciones perdedoras.

Enfoque Matemático

1) Valores de Grundy para un solo montón

Los movimientos posibles son

$$n\to n-1,\ n-4,\ n-9,\dots$$

de modo que

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

2) Por qué tres montones se reducen a xor

La posición total es la suma disjunta de tres juegos imparciales, así que una terna es perdedora exactamente cuando

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) Ejemplo pequeño

Los primeros valores son

$$g(0..4)=(0,1,0,1,2).$$

Para \(N=4\) hay exactamente 12 ternas perdedoras. Este ejemplo deja claro que el verdadero problema no es la teoría de juegos, sino contar eficientemente todas las ternas con xor nulo.

4) El conteo directo sería cúbico

Recorrer todas las ternas \((a,b,c)\) costaría

$$O(N^3),$$

lo cual es inviable para \(N=100000\).

5) Conteo con prefijos y sufijos para un \(b\) fijo

Fijamos el montón central \(b\). Entonces

$$0\le a\le b,\qquad b\le c\le N.$$

Definimos

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

Como la condición perdedora es

$$g(a)\oplus g(b)\oplus g(c)=0,$$

si \(x=g(a)\), entonces debe cumplirse

$$g(c)=x\oplus g(b).$$

Por tanto, la contribución del valor fijo \(b\) es

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

6) Por qué esto respeta automáticamente \(a\le b\le c\)

Antes de contar para un \(b\), el código añade \(g(b)\) al prefijo. Durante el conteo, el prefijo representa exactamente \(a\in[0,b]\), mientras que el sufijo todavía representa \(c\in[b,N]\). Solo después se elimina \(b\) del sufijo. Así, la restricción de orden está incorporada en el orden de actualización.

Cómo Funciona el Código

1) Tabla de Grundy. build_grundy(limit) calcula todos los valores con una estructura de mex por marcas temporales.

2) Tamaño del alfabeto Grundy. Luego se calcula el máximo valor de Grundy observado para dimensionar los arreglos de frecuencias.

3) Inicialización del sufijo. Al principio, el lado derecho contiene todos los tamaños \(0,\dots,N\).

4) Barrido por \(b\). Para cada \(b\), se actualiza el prefijo, se suma \(\text{add}_b\) y finalmente se ajusta el sufijo.

5) Checkpoints. La versión en C++ verifica

$$\texttt{solve(29)}=1160$$

y además compara \(\texttt{solve(40)}\) con un brute force completo.

6) Valor final. Para \(N=100000\), la respuesta es

$$2586528661783.$$

Complejidad

La precalculación de Grundy cuesta aproximadamente \(O(N\sqrt N)\), porque para cada \(n\) se inspeccionan todos los cuadrados hasta \(n\). El barrido de conteo cuesta \(O(N\cdot G)\), donde \(G\) es el número de valores Grundy distintos y resulta pequeño en la práctica. La memoria es \(O(N+G)\).

Lecturas

  1. Problema: https://projecteuler.net/problem=310
  2. Teorema de Sprague-Grundy: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Juego subtract-a-square: https://en.wikipedia.org/wiki/Subtract_a_square

问题概述

在 Nim Square 中,每一步可以从大小为 \(n\) 的一堆中拿走任意一个不超过它的正平方数 \(k^2\)。题目要求统计满足

$$0\le a\le b\le c\le N$$

的三元组里,有多少是必败态。

数学方法

1) 单堆的 Grundy 递推

单堆允许的操作是

$$n\to n-1,\ n-4,\ n-9,\dots$$

因此

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

2) 为什么三堆只看 xor

整个局面是三个无偏子博弈的直和,所以三元组 \((a,b,c)\) 是必败态,当且仅当

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) 一个小例子

前几个单堆 Grundy 值为

$$g(0..4)=(0,1,0,1,2).$$

当 \(N=4\) 时,必败三元组一共有 12 个。这个例子说明,难点不在 Grundy 理论本身,而在如何高效统计所有 xor 为 0 的有序三元组。

4) 朴素计数是立方级

直接枚举所有 \((a,b,c)\) 的代价是

$$O(N^3),$$

对 \(N=100000\) 完全不可行。

5) 固定中间堆 \(b\) 的前缀/后缀计数

固定中间值 \(b\) 后,有

$$0\le a\le b,\qquad b\le c\le N.$$

定义频数

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

由于必败条件是

$$g(a)\oplus g(b)\oplus g(c)=0,$$

若 \(x=g(a)\),则必须有

$$g(c)=x\oplus g(b).$$

因此固定 \(b\) 的贡献为

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

6) 为什么它天然满足 \(a\le b\le c\)

实现顺序非常关键。程序先把 \(g(b)\) 加入前缀,因此计数时前缀正好代表 \(a\in[0,b]\)。同时此时后缀仍然包含 \(c\in[b,N]\)。计数完成之后,才把 \(b\) 从后缀里移除。于是顺序约束完全由更新顺序自动保证。

代码如何工作

1) 构造 Grundy 表。 build_grundy(limit) 使用带时间戳的 mex 数组计算全部单堆值。

2) 统计 Grundy 值范围。 然后找出出现过的最大 Grundy 值,以确定频数数组大小。

3) 初始化后缀。 初始时后缀包含所有堆大小 \(0,\dots,N\)。

4) 扫描中间下标。 对每个 \(b\),先更新前缀,再套用上式累加 \(\text{add}_b\),最后更新后缀。

5) 检查点。 C++ 版本验证了

$$\texttt{solve(29)}=1160$$

并把 \(\texttt{solve(40)}\) 与完整 brute force 比较。

6) 最终答案。 当 \(N=100000\) 时,程序返回

$$2586528661783.$$

复杂度分析

Grundy 预处理大约是 \(O(N\sqrt N)\),因为每个 \(n\) 都要查看不超过它的所有平方数。计数扫描是 \(O(N\cdot G)\),其中 \(G\) 是不同 Grundy 值的个数,而在实践中 \(G\) 很小。空间复杂度为 \(O(N+G)\)。

延伸阅读

  1. 题目页面: https://projecteuler.net/problem=310
  2. Sprague-Grundy 定理: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Subtract-a-square 游戏: https://en.wikipedia.org/wiki/Subtract_a_square

Краткое описание

В Nim Square из кучи размера \(n\) можно снять любой положительный квадрат \(k^2\le n\). Нужно посчитать, сколько троек

$$0\le a\le b\le c\le N$$

являются проигрышными позициями.

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

1) Числа Гранди для одной кучи

Разрешенные переходы имеют вид

$$n\to n-1,\ n-4,\ n-9,\dots$$

поэтому

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

2) Почему три кучи сводятся к xor

Вся позиция — это дизъюнктная сумма трех impartial games. Следовательно, тройка проигрышна тогда и только тогда, когда

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) Малый пример

Первые значения равны

$$g(0..4)=(0,1,0,1,2).$$

Для \(N=4\) получается ровно 12 проигрышных троек. Это показывает, что главный вопрос — не в самих значениях Гранди, а в быстром подсчете всех xor-нулевых троек.

4) Наивный подсчет был бы кубическим

Прямой перебор всех \((a,b,c)\) имеет стоимость

$$O(N^3),$$

что невозможно при \(N=100000\).

5) Префиксно-суффиксный счет для фиксированного \(b\)

Фиксируем среднюю кучу \(b\). Тогда

$$0\le a\le b,\qquad b\le c\le N.$$

Введем частоты

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

Из условия

$$g(a)\oplus g(b)\oplus g(c)=0$$

следует, что при \(x=g(a)\) нужно

$$g(c)=x\oplus g(b).$$

Поэтому вклад фиксированного \(b\) равен

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

6) Почему это автоматически соблюдает \(a\le b\le c\)

Перед подсчетом для данного \(b\) код сначала добавляет \(g(b)\) в префикс. Значит, во время счета префикс уже соответствует \(a\in[0,b]\), а суффикс еще соответствует \(c\in[b,N]\). Только после этого \(b\) удаляется из суффикса. Поэтому условие порядка встроено прямо в порядок обновлений.

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

1) Таблица Гранди. build_grundy(limit) вычисляет все значения через timestamp-массив для mex.

2) Размер алфавита Гранди. Затем находится максимальное встреченное значение Гранди, чтобы задать размер частотных массивов.

3) Инициализация суффикса. Сначала правая сторона содержит все размеры \(0,\dots,N\).

4) Sweep по \(b\). Для каждого \(b\) сначала расширяется префикс, затем считается \(\text{add}_b\), затем обновляется суффикс.

5) Контрольные проверки. В C++ проверяются

$$\texttt{solve(29)}=1160$$

и точное совпадение \(\texttt{solve(40)}\) с brute-force.

6) Итоговое значение. Для \(N=100000\) программа возвращает

$$2586528661783.$$

Сложность

Предвычисление Гранди требует примерно \(O(N\sqrt N)\), так как для каждого \(n\) рассматриваются все квадраты до \(n\). Этап подсчета стоит \(O(N\cdot G)\), где \(G\) — число различных значений Гранди, и на практике оно невелико. Память: \(O(N+G)\).

Дополнительные материалы

  1. Условие: https://projecteuler.net/problem=310
  2. Теорема Шпрэге-Гранди: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. Игра subtract-a-square: https://en.wikipedia.org/wiki/Subtract_a_square

ملخص المسألة

في Nim Square يمكن إزالة أي مربع كامل موجب \(k^2\le n\) من كومة حجمها \(n\). المطلوب عدّ عدد الثلاثيات

$$0\le a\le b\le c\le N$$

التي تمثل أوضاعًا خاسرة.

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

1) قيم Grundy لكومة واحدة

الحركات الممكنة هي

$$n\to n-1,\ n-4,\ n-9,\dots$$

ولذلك

$$g(0)=0,$$

$$g(n)=\operatorname{mex}\{g(n-k^2):k^2\le n\}.$$

2) لماذا تختزل ثلاث أكوام إلى xor؟

الوضع الكلي هو مجموع منفصل لثلاث ألعاب حيادية. لذلك تكون الثلاثية خاسرة تمامًا عندما

$$g(a)\oplus g(b)\oplus g(c)=0.$$

3) مثال صغير

أول القيم هي

$$g(0..4)=(0,1,0,1,2).$$

وعند \(N=4\) نجد 12 ثلاثية خاسرة بالضبط. يوضح هذا أن الصعوبة الحقيقية ليست في نظرية Grundy نفسها، بل في العد السريع لجميع ثلاثيات xor الصفرية.

4) العد المباشر سيكون تكعيبيًا

الفحص المباشر لجميع \((a,b,c)\) يكلف

$$O(N^3),$$

وهو غير عملي عند \(N=100000\).

5) عدّ prefix/suffix عند تثبيت الوسط \(b\)

لنثبت الكومة الوسطى \(b\). عندئذ

$$0\le a\le b,\qquad b\le c\le N.$$

ونعرّف

$$\text{pref}[x]=\#\{a\le b:g(a)=x\},$$

$$\text{suf}[y]=\#\{c\ge b:g(c)=y\}.$$

وبما أن شرط الخسارة هو

$$g(a)\oplus g(b)\oplus g(c)=0,$$

فإذا كان \(x=g(a)\)، فلا بد أن

$$g(c)=x\oplus g(b).$$

إذًا تكون مساهمة هذا \(b\) هي

$$\text{add}_b=\sum_x \text{pref}[x]\cdot \text{suf}[x\oplus g(b)].$$

6) لماذا يضمن هذا تلقائيًا \(a\le b\le c\)؟

قبل الحساب عند قيمة \(b\)، يضيف الكود \(g(b)\) إلى prefix. لذلك أثناء العد يكون prefix ممثلًا تمامًا للفترة \(a\in[0,b]\)، بينما يظل suffix ممثلًا للفترة \(c\in[b,N]\). وبعد انتهاء العد فقط يُزال \(b\) من suffix. أي أن شرط الترتيب مدمج في ترتيب التحديثات نفسه.

كيف يعمل الكود

1) بناء جدول Grundy. تحسب الدالة build_grundy(limit) جميع القيم باستخدام مصفوفة mex مع طابع زمني.

2) تحديد حجم قيم Grundy. بعد ذلك يُحسب أكبر Grundy ظهر، من أجل تحديد حجم مصفوفتَي التكرار.

3) تهيئة suffix. في البداية يحتوي الطرف الأيمن على جميع القيم من \(0\) إلى \(N\).

4) المسح على \(b\). لكل \(b\)، يُحدَّث prefix أولًا، ثم تُضاف مساهمة \(\text{add}_b\)، ثم يُحدَّث suffix.

5) نقاط التحقق. تتحقق نسخة ++C من أن

$$\texttt{solve(29)}=1160$$

كما تقارن \(\texttt{solve(40)}\) مع brute-force كامل.

6) القيمة النهائية. عندما \(N=100000\)، تعيد الخوارزمية

$$2586528661783.$$

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

حساب جدول Grundy يكلف تقريبًا \(O(N\sqrt N)\)، لأن كل \(n\) يفحص جميع المربعات التي لا تتجاوزه. أما مرحلة العد فتكلف \(O(N\cdot G)\)، حيث \(G\) هو عدد قيم Grundy المختلفة، وهو صغير عمليًا. الذاكرة المطلوبة هي \(O(N+G)\).

مراجع إضافية

  1. صفحة المسألة: https://projecteuler.net/problem=310
  2. نظرية Sprague-Grundy: https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
  3. لعبة Subtract-a-square: https://en.wikipedia.org/wiki/Subtract_a_square