Problem Summary

For a fixed \(n\), we want binary circles in which every \(n\)-bit block appears exactly once as a cyclic substring. The problem asks for the sum of the binary integers represented by all such circles, using the canonical representation chosen by the solver.

The implementation does not search the space of bit strings directly. Instead, it models the problem as an Eulerian-cycle search in a De Bruijn graph and enumerates every valid cycle with depth-first backtracking.

Mathematical Approach

1. From Binary Circles to De Bruijn Graphs

A binary circle of order \(n\) is a cyclic bit sequence whose cyclic length-\(n\) substrings are exactly the \(2^n\) possible \(n\)-bit patterns. That condition is naturally encoded by the De Bruijn graph of order \(n\).

The graph has:

$$|V|=2^{n-1},\qquad |E|=2^n.$$

Each vertex is an \((n-1)\)-bit string. Each edge is an \(n\)-bit string, viewed as a transition from its first \(n-1\) bits to its last \(n-1\) bits.

2. Edge Transition Rule

If the current node is \(u\) and we append a bit \(b\in\{0,1\}\), then the next edge label is

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

and the next node is its suffix of length \(n-1\):

$$v=e\ \&\ (2^{n-1}-1).$$

So the DFS is literally walking through the De Bruijn graph one edge at a time, where every edge corresponds to one \(n\)-bit block.

3. Why Eulerian Cycles Are Exactly the Answers

Traversing every edge exactly once means that every \(n\)-bit pattern appears exactly once. That is precisely the definition of the required binary circle.

Conversely, any binary circle determines a closed walk in the graph, because consecutive length-\(n\) windows overlap in their last \(n-1\) bits. Therefore the problem is equivalent to enumerating Eulerian cycles in this directed graph.

4. Canonical Rooting at \(0^n\)

Cycles are circular, so the same cycle can be rotated. The solver removes this rotational ambiguity by fixing the starting block to be \(0^n\). In the code this is done by marking edge 0 as already used and starting the DFS at node 0.

This anchor gives one canonical representative per cycle. Without it, the same circle would be found multiple times under different rotations.

5. Depth-First Search With Used-Edge Tracking

The recursive search keeps:

1. a boolean array of used edges,

2. the current node,

3. the partial bit sequence being built.

At each step it tries bit 0 and bit 1. If the corresponding edge has not been used, it marks the edge, appends the bit, and recurses to the suffix node. After returning, it undoes the choice and continues with the next branch.

This is a standard backtracking enumeration of all Eulerian cycles from the fixed root.

6. Why the Search Is Correct

The invariant is simple: the set of used edges is exactly the set of edges already traversed in the partial walk, and the current node is the suffix of the last traversed edge.

If all \(2^n\) edges have been used and the walk returns to node 0, then the partial walk is a closed Eulerian cycle. If any edge remains unused, then the walk is incomplete and cannot yet represent a valid binary circle.

Because the DFS tries every unused outgoing edge at every step, every Eulerian cycle rooted at \(0^n\) is enumerated exactly once.

7. Converting a Cycle to an Integer

The code stores the current bit stream in sequence. Once a full cycle has been found, the first \(2^n\) bits of that stream are interpreted as a binary integer:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

That value is added to the running total. The solver therefore sums the canonical integer representative of every valid binary circle.

A tiny worked example is \(n=2\). The unique anchored cycle produces the bit prefix

$$0011,$$

whose cyclic windows are

$$00,\ 01,\ 11,\ 10.$$

These are exactly the four 2-bit patterns, and the canonical integer is

$$0011_2=3,$$

which matches the checkpoint \(S(2)=3\).

8. Small Checkpoints

The program verifies two sample values:

$$S(2)=3,\qquad S(3)=52.$$

These checks are useful because they confirm both the graph construction and the integer-conversion logic before larger searches are attempted.

For \(n=3\), the two canonical cycles found by the solver are

$$00010111_2=23,\qquad 00011101_2=29,$$

so

$$S(3)=23+29=52.$$

How the Code Works

The program accepts --n and --skip-checkpoints. solve(n) first computes the edge count \(2^n\) and the node mask \(2^{n-1}-1\). The array used_edge marks which \(n\)-bit blocks have already been traversed. The vector sequence starts with \(n\) zero bits, which is the anchored \(0^n\) block.

The lambda dfs explores the two possible extensions from the current node. When a candidate edge is unused, it is marked, its trailing bit is appended, and the recursion continues from the suffix node. Once all edges are used and the walk has returned to node 0, the solver converts the collected bit stream into a binary integer and adds it to total.

The checkpoint routine simply compares the results of solve(2) and solve(3) against the expected sample values.

Complexity Analysis

The solver enumerates Eulerian cycles by backtracking, so the running time is exponential in \(n\). That is unavoidable here because the number of valid binary circles itself grows rapidly with \(n\).

Memory usage is \(O(2^n)\), dominated by the edge-usage array and the current bit sequence. The recursion depth is also proportional to the cycle length.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=265
  2. De Bruijn sequence: Wikipedia - De Bruijn sequence
  3. Eulerian path/cycle: Wikipedia - Eulerian path

Problemzusammenfassung

Für ein festes \(n\) werden binäre Kreise gesucht, in denen jedes \(n\)-Bit-Muster genau einmal als zyklischer Teilstring vorkommt. Das Programm summiert die binären Ganzzahlen, die durch alle solchen Kreise repräsentiert werden.

Statt Bitfolgen direkt zu durchsuchen, formuliert der Solver das Problem als Eulerkreis-Suche in einem De-Bruijn-Graphen und enumeriert alle Lösungen per DFS.

Mathematischer Ansatz

1. Von binären Kreisen zu De-Bruijn-Graphen

Ein binärer Kreis der Ordnung \(n\) ist eine zyklische Bitfolge, deren zyklische Länge-\(n\)-Fenster genau die \(2^n\) möglichen \(n\)-Bit-Muster bilden. Genau das wird durch den De-Bruijn-Graphen der Ordnung \(n\) modelliert.

Der Graph besitzt

$$|V|=2^{n-1},\qquad |E|=2^n.$$

Jeder Knoten ist ein \((n-1)\)-Bit-Wort, jede Kante ein \(n\)-Bit-Wort.

2. Übergangsregel

Aus dem aktuellen Knoten \(u\) und einem angehängten Bit \(b\in\{0,1\}\) entsteht die Kante

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

und der Folgeknoten ist der Suffix-Knoten

$$v=e\ \&\ (2^{n-1}-1).$$

Die DFS läuft also direkt über den Graphen und benutzt jede Kante als genau ein \(n\)-Bit-Fenster.

3. Warum Eulerkreise genau die Antworten sind

Jede Kante genau einmal zu durchlaufen bedeutet, dass jedes \(n\)-Bit-Muster genau einmal erscheint. Das ist exakt die gesuchte Bedingung.

Umgekehrt liefert jede gültige Kreisfolge einen geschlossenen Walk im Graphen, weil benachbarte \(n\)-Bit-Fenster um \(n-1\) Bits überlappen. Daher ist das Problem äquivalent zur Enumeration aller Eulerkreise.

4. Kanonischer Start bei \(0^n\)

Da Kreise rotierbar sind, würde dieselbe Lösung sonst mehrfach auftreten. Der Solver fixiert deshalb den Startblock \(0^n\). Im Code geschieht das durch das Vormarkieren von Kante 0 und den Start der DFS am Knoten 0.

Damit erhält jede Kreislösung genau einen kanonischen Repräsentanten.

5. DFS mit Kantenmarkierung

Die Rekursion verwaltet:

1. ein Bool-Array für benutzte Kanten,

2. den aktuellen Knoten,

3. die bisher aufgebaute Bitfolge.

Für jedes Bit 0 und 1 wird geprüft, ob die zugehörige Kante noch frei ist. Falls ja, wird sie markiert, das Bit angehängt und rekursiv weitergesucht. Nach der Rückkehr wird die Entscheidung zurückgenommen.

6. Korrektheit

Die Invariante ist klar: Benutzte Kanten sind genau die bereits durchlaufenen Kanten des partiellen Walks, und der aktuelle Knoten ist der Suffix der letzten Kante.

Sind alle \(2^n\) Kanten benutzt und ist der Walk wieder bei Knoten 0, dann ist ein Eulerkreis gefunden. Solange noch Kanten fehlen, ist die Folge unvollständig und noch kein gültiger Kreis.

Da die DFS an jeder Stelle alle unbenutzten Ausgänge ausprobiert, wird jeder bei \(0^n\) verankerte Eulerkreis genau einmal enumeriert.

7. Umrechnung in eine Ganzzahl

Die aktuelle Bitfolge wird im Vektor sequence gespeichert. Nach einem vollständigen Kreis werden die ersten \(2^n\) Bits als Binärzahl gelesen:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

Dieser Wert wird zur Gesamtsumme addiert.

Ein kleines durchgerechnetes Beispiel ist \(n=2\). Der eindeutige verankerte Kreis erzeugt das Bitpräfix

$$0011,$$

dessen zyklische Fenster

$$00,\ 01,\ 11,\ 10$$

sind. Das sind genau alle vier 2-Bit-Muster, und der kanonische ganzzahlige Repräsentant ist

$$0011_2=3,$$

was exakt zum Checkpoint \(S(2)=3\) passt.

8. Kleine Prüfpunkte

Das Programm prüft die Beispielwerte

$$S(2)=3,\qquad S(3)=52.$$

So werden Graphkonstruktion und Zahlenumrechnung vor größeren Läufen verifiziert.

Für \(n=3\) findet der Solver die beiden kanonischen Kreise

$$00010111_2=23,\qquad 00011101_2=29,$$

also

$$S(3)=23+29=52.$$

Funktionsweise des Codes

Das Programm akzeptiert --n und --skip-checkpoints. solve(n) berechnet zunächst \(2^n\) und die Maske \(2^{n-1}-1\). Das Array used_edge markiert bereits besuchte \(n\)-Bit-Blöcke. Der Vektor sequence beginnt mit \(n\) Nullen und repräsentiert damit den verankerten Block \(0^n\).

Die Lambda-Funktion dfs probiert die beiden möglichen Fortsetzungen aus. Ist eine Kandidatenkante noch unbenutzt, wird sie markiert, ihr letztes Bit angehängt und dann vom Folgeknoten aus weitergesucht. Sobald alle Kanten benutzt sind und der Walk zum Knoten 0 zurückkehrt, wird die Bitfolge in eine Ganzzahl umgerechnet und zu total addiert.

Die Checkpoints vergleichen lediglich solve(2) und solve(3) mit den bekannten Werten.

Komplexitätsanalyse

Die Suche enumeriert Eulerkreise per Backtracking, daher ist die Laufzeit exponentiell in \(n\). Das ist hier unvermeidlich, weil auch die Anzahl der gültigen Kreise schnell wächst.

Der Speicherbedarf ist \(O(2^n)\), dominiert durch Kantenmarkierungen und die aktuelle Bitfolge. Auch die Rekursionstiefe wächst proportional zur Kreislänge.

Fußnoten und Quellen

  1. Problemseite: https://projecteuler.net/problem=265
  2. De-Bruijn-Folge: Wikipedia - De-Bruijn-Folge
  3. Eulerkreis: Wikipedia - Eulerscher_Weg

Problem Özeti

Sabit bir \(n\) için, her \(n\)-bit bloğunun döngü üzerinde tam bir kez göründüğü ikili çevrimler aranır. Çözüm, bu çevrimlerin temsil ettiği tamsayıları toplar.

Kod, bit dizilerini doğrudan taramak yerine problemi De Bruijn grafı üzerinde Euler çevrimi sayımına dönüştürür ve bütün geçerli çevrimleri DFS ile üretir.

Matematiksel Yaklaşım

1. Binary circle'dan De Bruijn grafına

Derecesi \(n\) olan bir binary circle, döngüsel uzunluğu \(n\) olan blokların tüm \(2^n\) olası \(n\)-bit desenleri birer kez içerdiği çevrimsel bir bit dizisidir. Bu yapı De Bruijn grafıyla tam olarak modellenir.

Grafın boyutları:

$$|V|=2^{n-1},\qquad |E|=2^n.$$

Her düğüm \(n-1\) bitlik bir durumdur, her kenar ise \(n\) bitlik bir geçiştir.

2. Geçiş kuralı

Mevcut düğüm \(u\) iken bit \(b\in\{0,1\}\) eklenirse yeni kenar

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

ve yeni düğüm

$$v=e\ \&\ (2^{n-1}-1)$$

olur. DFS bu geçişi birebir uygular.

3. Neden Euler çevrimi?

Her kenarı tam bir kez gezmek, her \(n\)-bit desenin tam bir kez görünmesi demektir. Bu da istenen koşuldur.

Tersine, geçerli her çevrim art arda gelen \(n\)-bit pencerelerin \(n-1\) bit üst üste binmesi nedeniyle graf üzerinde kapalı bir yol verir. Yani cevaplar tam olarak Euler çevrimleridir.

4. Kanonik başlangıç

Döngüsel yapıda aynı çözüm farklı rotasyonlarla tekrar eder. Bu tekrarları kaldırmak için başlangıç bloğu \(0^n\) sabitlenir. Kodda bu, 0 numaralı kenarın baştan kullanılmış işaretlenmesi ve DFS'nin 0 düğümünden başlamasıyla sağlanır.

5. DFS ve kullanılan kenarlar

Özyineleme üç şeyi saklar:

1. kullanılmış kenarlar,

2. mevcut düğüm,

3. kurulan bit dizisi.

Her adımda 0 ve 1 denenir. Uygun kenar kullanılmamışsa işaretlenir, bit eklenir ve bir sonraki düğüme geçilir. Geri dönünce seçim geri alınır.

6. Doğruluk

İndüksiyon değişmezi şudur: kullanılan kenarlar, kısmi turun geçtiği kenarlarla aynıdır ve mevcut düğüm son kenarın suffix'idir.

Tüm \(2^n\) kenar kullanılmış ve yol tekrar 0 düğümüne dönmüşse bir Euler çevrimi bulunmuştur. Henüz kullanılmamış kenar varsa tur tamamlanmamıştır.

DFS her düğümde tüm kullanılmamış çıkışları denediği için, 0^n'e sabitlenmiş her Euler çevrimi tam bir kez üretilir.

7. Sayıya dönüştürme

Geçerli tur bulununca bit dizisi sequence içinde tutulur. Sonra ilk \(2^n\) bit ikili sayı olarak okunur:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

Bu değer toplam cevaba eklenir.

Küçük bir çalışan örnek \(n=2\) durumudur. Tek kanonik çevrim şu bit önekini üretir:

$$0011,$$

bunun döngüsel pencereleri de

$$00,\ 01,\ 11,\ 10$$

olur. Bunlar dört adet 2-bit desenin tamamıdır. Kanonik tamsayı da

$$0011_2=3$$

olur; bu da \(S(2)=3\) checkpoint'iyle tam uyuşur.

8. Kontrol örnekleri

Program şu örnekleri doğrular:

$$S(2)=3,\qquad S(3)=52.$$

Böylece hem grafik kurulumu hem de sayı üretimi test edilmiş olur.

\(n=3\) için solver'ın bulduğu iki kanonik çevrim

$$00010111_2=23,\qquad 00011101_2=29$$

olduğundan

$$S(3)=23+29=52$$

elde edilir.

Kodun Çalışma Mantığı

Program --n ve --skip-checkpoints seçeneklerini destekler. solve(n) önce \(2^n\) kenar sayısını ve \(2^{n-1}-1\) maske değerini hesaplar. used_edge dizisi hangi \(n\)-bit bloklarının kullanıldığını tutar. sequence vektörü \(n\) sıfır bit ile başlar; bu da sabitlenmiş \(0^n\) bloğudur.

dfs lambdası mevcut düğümden 0 ve 1 uzantılarını dener. Uygun kenar boştaysa işaretlenir, son biti eklenir ve komşu düğüme özyinelemeli geçilir. Tüm kenarlar bittiğinde ve düğüm 0'a döndüğünde, oluşturulan bit dizisi sayıya çevrilip total'a eklenir.

Checkpoint rutini yalnızca solve(2) ve solve(3) sonuçlarını beklenen değerlerle karşılaştırır.

Karmaşıklık Analizi

Arama, Euler çevrimlerini backtracking ile ürettiği için zaman karmaşıklığı \(n\)'e göre üstel büyür. Bu kaçınılmazdır; çünkü geçerli binary circle sayısı da hızla artar.

Bellek kullanımı, kenar işaretleri ve geçici bit dizisi nedeniyle \(O(2^n)\)'dir. Özyineleme derinliği de tur uzunluğu kadar büyür.

Dipnotlar ve Kaynakça

  1. Problem sayfası: https://projecteuler.net/problem=265
  2. De Bruijn dizisi: Wikipedia - De Bruijn dizisi
  3. Euler yolu/çevrimi: Wikipedia - Euler yolu

Resumen del Problema

Para un \(n\) fijo, se buscan ciclos binarios en los que cada bloque de \(n\) bits aparezca exactamente una vez como subcadena cíclica. La solución suma los enteros binarios representados por todos esos ciclos.

El programa no explora cadenas de bits directamente; traduce el problema a un conteo de ciclos eulerianos en un grafo de De Bruijn y los enumera con DFS.

Enfoque Matemático

1. De círculos binarios a grafos de De Bruijn

Un círculo binario de orden \(n\) es una secuencia cíclica en la que los bloques cíclicos de longitud \(n\) son exactamente los \(2^n\) patrones posibles. Eso se modela de forma natural con el grafo de De Bruijn de orden \(n\).

El grafo tiene

$$|V|=2^{n-1},\qquad |E|=2^n.$$

Cada vértice es una cadena de \(n-1\) bits y cada arista es una cadena de \(n\) bits.

2. Regla de transición

Si el nodo actual es \(u\) y se añade un bit \(b\in\{0,1\}\), la arista es

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

y el nuevo nodo es

$$v=e\ \&\ (2^{n-1}-1).$$

La DFS sigue exactamente esta transición.

3. Por qué los ciclos eulerianos son la respuesta

Recorrer cada arista exactamente una vez equivale a ver cada patrón de \(n\) bits exactamente una vez. Esa es justo la condición del problema.

Recíprocamente, cualquier ciclo válido produce un paseo cerrado en el grafo porque las ventanas consecutivas de longitud \(n\) se solapan en \(n-1\) bits. Por tanto, la tarea es enumerar ciclos eulerianos.

4. Anclaje canónico en \(0^n\)

Como un ciclo puede rotarse, la misma solución aparecería varias veces. Para eliminar ese duplicado, el solver fija el bloque inicial \(0^n\). En el código esto se hace marcando la arista 0 como usada y empezando la DFS en el nodo 0.

5. DFS con marcas de aristas

La búsqueda mantiene:

1. un arreglo booleano de aristas usadas,

2. el nodo actual,

3. la secuencia parcial de bits.

En cada paso prueba los bits 0 y 1. Si la arista correspondiente no ha sido usada, la marca, añade el bit y continúa recursivamente. Al volver, deshace la elección.

6. Corrección

La invariante es simple: las aristas usadas son exactamente las ya recorridas en el paseo parcial, y el nodo actual es el sufijo de la última arista.

Si se han usado las \(2^n\) aristas y el paseo vuelve al nodo 0, entonces se ha encontrado un ciclo euleriano. Si aún queda alguna arista sin usar, el paseo no está completo.

Como la DFS prueba todas las salidas no usadas en cada estado, cada ciclo euleriano anclado en \(0^n\) se genera exactamente una vez.

7. Conversión a entero

La secuencia de bits se guarda en sequence. Cuando se encuentra un ciclo completo, los primeros \(2^n\) bits se interpretan como un entero binario:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

Ese valor se suma al total.

Un ejemplo pequeño completamente resuelto es \(n=2\). El único ciclo anclado produce el prefijo de bits

$$0011,$$

cuyas ventanas cíclicas son

$$00,\ 01,\ 11,\ 10.$$

Esos son exactamente los cuatro patrones de 2 bits, y el entero canónico es

$$0011_2=3,$$

lo que coincide exactamente con el checkpoint \(S(2)=3\).

8. Puntos de comprobación

El programa comprueba

$$S(2)=3,\qquad S(3)=52.$$

Así se valida tanto el grafo como la conversión final.

Para \(n=3\), el solver encuentra los dos ciclos canónicos

$$00010111_2=23,\qquad 00011101_2=29,$$

por lo tanto

$$S(3)=23+29=52.$$

Cómo funciona el código

El programa acepta --n y --skip-checkpoints. solve(n) calcula primero el número de aristas \(2^n\) y la máscara de nodos \(2^{n-1}-1\). used_edge marca qué bloques de \(n\) bits ya se han visitado. sequence empieza con \(n\) ceros, que representan el bloque anclado \(0^n\).

La lambda dfs explora las dos extensiones posibles desde el nodo actual. Si una arista candidata está libre, se marca, se añade su último bit y se continúa desde el nodo sufijo. Cuando todas las aristas están usadas y el paseo regresa a 0, la secuencia se convierte en entero y se suma a total.

Los checkpoints sólo comparan solve(2) y solve(3) con sus valores esperados.

Complejidad

La búsqueda enumera ciclos eulerianos mediante backtracking, así que el tiempo es exponencial en \(n\). Esto es inevitable porque el número de ciclos válidos también crece muy rápido.

La memoria es \(O(2^n)\), dominada por las marcas de aristas y la secuencia actual de bits. La profundidad de la recursión también es proporcional a la longitud del ciclo.

Referencias

  1. Problema: https://projecteuler.net/problem=265
  2. Secuencia de De Bruijn: Wikipedia - Secuencia de De Bruijn
  3. Camino/ciclo euleriano: Wikipedia - Camino euleriano

问题概述

给定固定的 \(n\),要求找出那些二进制环:在环上每个 \(n\)-bit 块都恰好出现一次。程序把每个合法环解释成一个二进制整数,并把所有这些整数求和。

代码并不是直接枚举位串,而是把问题转化为 De Bruijn 图上的 Euler 回路搜索,再用 DFS 回溯枚举所有合法回路。

数学方法

1. 从二进制环到 De Bruijn 图

一个 \(n\) 阶二进制环,是一个循环位串,其中所有循环长度为 \(n\) 的子串恰好对应全部 \(2^n\) 种 \(n\)-bit 模式。这正好由 De Bruijn 图建模。

图的规模为

$$|V|=2^{n-1},\qquad |E|=2^n.$$

每个顶点是 \(n-1\) 位字符串,每条边是 \(n\) 位字符串。

2. 转移规则

若当前节点是 \(u\),并追加位 \(b\in\{0,1\}\),则得到边

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

下一节点是其长度为 \(n-1\) 的后缀:

$$v=e\ \&\ (2^{n-1}-1).$$

DFS 正是按这个规则沿图移动。

3. 为什么 Euler 回路就是答案

每条边恰好走一次,就意味着每个 \(n\)-bit 模式恰好出现一次,这正是题目的要求。

反过来,任何合法二进制环都会在图上形成闭合走法,因为相邻的 \(n\)-bit 窗口会重叠 \(n-1\) 位。所以问题等价于枚举 Euler 回路。

4. 固定起点 \(0^n\)

环是可以旋转的,因此同一个解会出现多次。为了去掉旋转重复,程序把起始块固定为 \(0^n\)。代码里对应的是:边 0 先标记已使用,DFS 从节点 0 开始。

5. DFS 和边标记

递归搜索维护三样东西:

1. 已使用边的布尔数组,

2. 当前节点,

3. 正在构造的位序列。

每一步尝试 0 和 1 两个分支。如果对应边未被使用,就标记它、追加该位,再递归进入后缀节点。回溯时撤销选择。

6. 正确性

不变式很简单:已用边正好是部分路径经过的边,而当前节点就是最后一条边的后缀。

如果所有 \(2^n\) 条边都已经用完,并且路径回到节点 0,那么就找到了一条 Euler 回路。若还有边未用完,则路径还不完整,不能算合法答案。

由于 DFS 在每一步都尝试所有未使用的出边,所以每个固定起点为 \(0^n\) 的 Euler 回路都会且只会被枚举一次。

7. 转成整数

位串保存在 sequence 中。找到完整回路后,前 \(2^n\) 位被解释为一个二进制整数:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

然后把这个值加到总和里。

一个很小的完整例子是 \(n=2\)。唯一的锚定回路给出位前缀

$$0011,$$

它的循环窗口是

$$00,\ 01,\ 11,\ 10.$$

这正好是全部四种 2-bit 模式,而其规范整数就是

$$0011_2=3,$$

与 checkpoint \(S(2)=3\) 完全一致。

8. 校验点

程序检查两个样例:

$$S(2)=3,\qquad S(3)=52.$$

这可以同时验证图建模和数值转换是否正确。

当 \(n=3\) 时,solver 找到的两个规范回路是

$$00010111_2=23,\qquad 00011101_2=29,$$

所以

$$S(3)=23+29=52.$$

代码如何工作

程序支持 --n--skip-checkpointssolve(n) 先计算边数 \(2^n\) 和节点掩码 \(2^{n-1}-1\)。used_edge 记录每条 \(n\)-bit 边是否已经走过。sequence 初始为 \(n\) 个 0,也就是锚定的 \(0^n\) 块。

lambda dfs 会从当前节点尝试两个扩展。如果候选边还没用过,就把它标记、加入最后一位,然后从后缀节点继续搜索。等所有边都使用完且路径回到 0 节点时,位序列被转成整数并加入 total

检查点只比较 solve(2)solve(3) 与预期值是否一致。

复杂度分析

搜索使用回溯枚举 Euler 回路,因此时间复杂度对 \(n\) 是指数级的。这是不可避免的,因为合法二进制环的数量本身也增长很快。

空间复杂度为 \(O(2^n)\),主要来自边标记数组和当前位序列。递归深度也与回路长度成正比。

参考资料

  1. 题目页面: https://projecteuler.net/problem=265
  2. De Bruijn 序列: Wikipedia - De Bruijn 序列
  3. Euler 回路: Wikipedia - 欧拉回路

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

Для фиксированного \(n\) нужно найти бинарные циклы, в которых каждый \(n\)-битный блок встречается ровно один раз по окружности. Программа суммирует целые числа, представленные всеми такими циклами в канонической записи.

Вместо прямого перебора битовых строк решение сводит задачу к поиску эйлеровых циклов в графе де Брёйна и перечисляет все подходящие циклы с помощью DFS.

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

1. От бинарных кругов к графу де Брёйна

Бинарный круг порядка \(n\) - это циклическая битовая последовательность, у которой циклические блоки длины \(n\) образуют все \(2^n\) возможных шаблонов. Это естественно моделируется графом де Брёйна порядка \(n\).

Размеры графа:

$$|V|=2^{n-1},\qquad |E|=2^n.$$

Каждая вершина - это строка длины \(n-1\), каждое ребро - строка длины \(n\).

2. Правило перехода

Если текущий узел равен \(u\) и мы добавляем бит \(b\in\{0,1\}\), то получаем ребро

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

а следующий узел равен его суффиксу длины \(n-1\):

$$v=e\ \&\ (2^{n-1}-1).$$

DFS буквально идет по этим переходам.

3. Почему ответы - это эйлеровы циклы

Проход по каждому ребру ровно один раз означает, что каждый \(n\)-битный шаблон встречается ровно один раз. Это и есть требуемое условие.

Обратно, любой корректный бинарный круг задает замкнутый обход графа, потому что соседние окна длины \(n\) перекрываются на \(n-1\) битах. Значит, задача эквивалентна перечислению эйлеровых циклов.

4. Канонический старт в \(0^n\)

Поскольку цикл можно вращать, одна и та же последовательность появлялась бы много раз. Чтобы убрать эту повторяемость, solver фиксирует начальный блок \(0^n\). В коде это реализовано как заранее использованное ребро 0 и старт DFS из узла 0.

5. DFS и отметки рёбер

Рекурсия хранит три вещи:

1. массив использованных рёбер,

2. текущий узел,

3. текущую битовую последовательность.

На каждом шаге пробуются биты 0 и 1. Если соответствующее ребро свободно, оно помечается, бит добавляется, и поиск продолжается от суффиксного узла. При возврате выбор откатывается.

6. Корректность

Инвариант прост: использованные рёбра - это ровно рёбра, уже пройденные частичным путём, а текущий узел - суффикс последнего ребра.

Если использованы все \(2^n\) рёбра и путь возвращается в узел 0, найден эйлеров цикл. Если хотя бы одно ребро ещё свободно, путь неполон.

Поскольку DFS в каждой точке пробует все неиспользованные выходы, каждый эйлеров цикл, закреплённый за \(0^n\), перечисляется ровно один раз.

7. Преобразование в число

Битовая последовательность хранится в sequence. После нахождения полного цикла первые \(2^n\) бит интерпретируются как двоичное число:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

Это значение добавляется к сумме.

Небольшой полностью разобранный пример - случай \(n=2\). Единственный закреплённый цикл даёт битовый префикс

$$0011,$$

чьи циклические окна равны

$$00,\ 01,\ 11,\ 10.$$

Это ровно все четыре 2-битных шаблона, а канонический целочисленный представитель равен

$$0011_2=3,$$

что точно совпадает с checkpoint'ом \(S(2)=3\).

8. Контрольные значения

Программа проверяет:

$$S(2)=3,\qquad S(3)=52.$$

Так проверяются и граф, и преобразование в число.

Для \(n=3\) solver находит два канонических цикла

$$00010111_2=23,\qquad 00011101_2=29,$$

следовательно

$$S(3)=23+29=52.$$

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

Программа поддерживает параметры --n и --skip-checkpoints. solve(n) сначала вычисляет число рёбер \(2^n\) и маску узла \(2^{n-1}-1\). Массив used_edge хранит, какие \(n\)-битные блоки уже использованы. Вектор sequence начинается с \(n\) нулей, то есть с анкерованного блока \(0^n\).

Lambda dfs пробует два продолжения из текущего узла. Если кандидатное ребро ещё не использовано, оно помечается, его последний бит добавляется, и поиск продолжается от суффиксного узла. Когда все рёбра использованы и путь возвращается к 0, последовательность превращается в целое число и добавляется к total.

Проверки сравнивают solve(2) и solve(3) с ожидаемыми значениями.

Сложность

Поиск перечисляет эйлеровы циклы методом backtracking, поэтому время экспоненциально по \(n\). Это неизбежно, потому что число допустимых бинарных кругов тоже растёт очень быстро.

Память составляет \(O(2^n)\), в основном за счёт массива отметок рёбер и текущей битовой последовательности. Глубина рекурсии пропорциональна длине цикла.

Источники

  1. Условие: https://projecteuler.net/problem=265
  2. Последовательность де Брёйна: Wikipedia - Последовательность де Брёйна
  3. Эйлеров цикл: Wikipedia - Эйлеров цикл

ملخص المسألة

لـ \(n\) ثابت، نبحث عن دورات ثنائية يظهر فيها كل نمط بطول \(n\) بت مرة واحدة فقط على نحو دائري. ثم نجمع الأعداد الصحيحة الثنائية التي تمثلها جميع هذه الدورات في التمثيل الكانوني الذي يعتمده الحل.

لا يتعامل البرنامج مع سلاسل البتات بشكل مباشر؛ بل يحول المسألة إلى عدّ دورات Euler في رسم De Bruijn ثم يعدد كل دورة صحيحة ببحث عمقي رجوعي.

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

1. من الدورات الثنائية إلى رسم De Bruijn

الدورة الثنائية من الرتبة \(n\) هي سلسلة بتات دائرية، تكون فيها المقاطع الدائرية بطول \(n\) هي بالضبط كل الأنماط الممكنة وعددها \(2^n\). هذا يُنمذج طبيعيًا برسم De Bruijn من الرتبة \(n\).

أبعاد الرسم هي:

$$|V|=2^{n-1},\qquad |E|=2^n.$$

كل عقدة تمثل سلسلة بطول \(n-1\) بت، وكل حافة تمثل سلسلة بطول \(n\) بت.

2. قاعدة الانتقال

إذا كانت العقدة الحالية \(u\) وأضفنا البت \(b\in\{0,1\}\)، فإن الحافة الناتجة هي

$$e=((u\ll 1)\,|\,b)\ \&\ (2^n-1),$$

والعقدة التالية هي لاحقتها بطول \(n-1\):

$$v=e\ \&\ (2^{n-1}-1).$$

الـ DFS يتبع هذه القاعدة حرفيًا.

3. لماذا الجواب هو دورة Euler

المرور على كل حافة مرة واحدة فقط يعني أن كل نمط بطول \(n\) يظهر مرة واحدة فقط. وهذه هي بالضبط شروط المسألة.

وعكس ذلك صحيح أيضًا: أي دورة صالحة تعطي مسارًا مغلقًا في الرسم، لأن النوافذ المتتالية بطول \(n\) تتداخل في \(n-1\) بت. لذا فالمطلوب هو تعداد دورات Euler.

4. التثبيت الكانوني عند \(0^n\)

بما أن الدورة قابلة للدوران، فالنمط نفسه سيظهر بعدة إزاحات. لإزالة هذا التكرار يثبت الحل البداية عند \(0^n\). وفي الكود يتم ذلك عبر تعليم الحافة 0 كأنها مستخدمة مسبقًا والبدء من العقدة 0.

5. الـ DFS وتعليم الحواف

يحتفظ البحث الرجوعي بثلاثة أشياء:

1. مصفوفة منطقية للحواف المستخدمة،

2. العقدة الحالية،

3. تسلسل البتات الجاري بناؤه.

في كل خطوة تُجرَّب القيمتان 0 و1. إذا كانت الحافة المقابلة غير مستخدمة، تُعلَّم، ويُضاف آخر بت، ثم يستمر البحث من عقدة اللاحقة. وعند الرجوع تُلغى هذه الاختيارات.

6. صحة الخوارزمية

الثابت (invariant) بسيط: الحواف المستخدمة هي نفسها الحواف التي مر بها المسار الجزئي، والعقدة الحالية هي لاحقة آخر حافة.

إذا استُخدمت جميع الحواف \(2^n\) وعاد المسار إلى العقدة 0، فلدينا دورة Euler كاملة. وإذا بقيت حافة غير مستخدمة فالمسار غير مكتمل.

وبما أن الـ DFS يجرّب جميع الخروجيات غير المستخدمة في كل حالة، فإن كل دورة Euler مثبتة عند \(0^n\) تُولَّد مرة واحدة فقط.

7. التحويل إلى عدد صحيح

يُخزن تسلسل البتات في sequence. وبعد العثور على دورة كاملة، تُقرأ أول \(2^n\) بتات كعدد ثنائي:

$$X=\sum_{i=0}^{2^n-1} b_i\,2^{2^n-1-i}.$$

ثم تُضاف هذه القيمة إلى المجموع الكلي.

مثال صغير محلول بالكامل هو الحالة \(n=2\). فالدورة المثبتة الوحيدة تعطي بادئة البتات

$$0011,$$

ونوافذها الدائرية هي

$$00,\ 01,\ 11,\ 10.$$

وهذه بالضبط جميع الأنماط الأربعة ذات الطول 2، والتمثيل الصحيح الكانوني هو

$$0011_2=3,$$

وهذا يطابق تمامًا نقطة التحقق \(S(2)=3\).

8. نقاط التحقق

يتحقق البرنامج من القيمتين:

$$S(2)=3,\qquad S(3)=52.$$

وهكذا يتم فحص بناء الرسم والتحويل العددي معًا.

ولـ \(n=3\) يجد الحل الدورتين الكانونيتين

$$00010111_2=23,\qquad 00011101_2=29,$$

ومن ثم

$$S(3)=23+29=52.$$

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

يدعم البرنامج الخيارين --n و--skip-checkpoints. تبدأ الدالة solve(n) بحساب عدد الحواف \(2^n\) وقناع العقد \(2^{n-1}-1\). المصفوفة used_edge تسجل أي مقاطع \(n\)-بت استُخدمت بالفعل. أما sequence فتبدأ بـ \(n\) أصفار، أي الكتلة المثبتة \(0^n\).

تجرب الدالة المجهولة dfs الامتدادين الممكنين من العقدة الحالية. إذا كانت الحافة المرشحة غير مستخدمة، تُعلَّم، ويُضاف آخر بت منها، ثم يستمر البحث من عقدة اللاحقة. وعندما تُستخدم كل الحواف ويعود المسار إلى 0، يتحول التسلسل إلى عدد صحيح ويضاف إلى total.

التحقق فقط يقارن solve(2) وsolve(3) بالقيم المتوقعة.

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

البحث يعدّد دورات Euler بالبحث الرجوعي، لذا فالزمن أُسّي بالنسبة إلى \(n\). وهذا لا يمكن تجنبه لأن عدد الدورات الثنائية الصحيحة نفسه ينمو بسرعة كبيرة.

الذاكرة من رتبة \(O(2^n)\)، ويسيطر عليها مصفوفة تعليم الحواف وتسلسل البتات الحالي. وعمق الاستدعاء الرجوعي يتناسب مع طول الدورة.

مراجع إضافية

  1. صفحة المسألة: https://projecteuler.net/problem=265
  2. متتالية De Bruijn: Wikipedia - متتالية دي بروان
  3. دورة Euler: Wikipedia - دورة أويلر