C #, n = 128 in ungefähr 2:40
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sandbox
{
class PPCG137436
{
public static void Main(string[] args)
{
if (args.Length == 0) args = new string[] { "1", "2", "4", "8", "16", "32", "64", "128" };
foreach (string arg in args)
{
Console.WriteLine(Count(new int[(int)(0.5 + Math.Log(int.Parse(arg)) / Math.Log(2))], 0));
}
}
static int Count(int[] periods, int idx)
{
if (idx == periods.Length)
{
//Console.WriteLine(string.Join(", ", periods));
return 1;
}
int count = 0;
int p = idx == 0 ? 1 : periods[idx - 1];
for (int q = p; q <= 1 << (idx + 1); q++)
{
periods[idx] = q;
if (q == p || q > 1 << idx || p + q - Gcd(p, q) > 1 << idx && UnificationPasses(periods, idx, q)) count += Count(periods, idx + 1);
}
return count;
}
private static int Gcd(int a, int b)
{
while (a > 0) { int tmp = a; a = b % a; b = tmp; }
return b;
}
private static bool UnificationPasses(int[] periods, int idx, int q)
{
UnionSet union = new UnionSet(1 << idx);
for (int i = 0; i <= idx; i++)
{
for (int j = 0; j + periods[i] < Math.Min(2 << i, 1 << idx); j++) union.Unify(j, j + periods[i]);
}
IDictionary<int, long> rev = new Dictionary<int, long>();
for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] = 0L;
for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] |= 1L << k;
long zeroes = rev[union.Find(0)]; // wlog the value at position 0 is 0
ISet<int> onesIndex = new HashSet<int>();
// This can be seen as the special case of the next loop where j == -1.
for (int i = 0; i < idx; i++)
{
if (periods[i] == 2 << i) onesIndex.Add((2 << i) - 1);
}
for (int j = 0; j < idx - 1 && periods[j] == 2 << j; j++)
{
for (int i = j + 1; i < idx; i++)
{
if (periods[i] == 2 << i)
{
for (int k = (1 << j) + 1; k <= 2 << j; k++) onesIndex.Add((2 << i) - k);
}
}
}
for (int i = 1; i < idx; i++)
{
if (periods[i] == 1) continue;
int d = (2 << i) - periods[i];
long dmask = (1L << d) - 1;
if (((zeroes >> 1) & (zeroes >> periods[i]) & dmask) == dmask) onesIndex.Add(periods[i] - 1);
}
long ones = 0L;
foreach (var key in onesIndex) ones |= rev[union.Find(key)];
if ((zeroes & ones) != 0) return false; // Definite contradiction!
rev.Remove(union.Find(0));
foreach (var key in onesIndex) rev.Remove(key);
long[] masks = System.Linq.Enumerable.ToArray(rev.Values);
int numFilteredMasks = 0;
long set = 0;
long M = 0;
for (int i = 1; i <= idx; i++)
{
if (periods[i - 1] == 1) continue;
// Sort the relevant masks to the start
if (i == idx) numFilteredMasks = masks.Length; // Minor optimisation: skip the filter because we know we need all the masks
long filter = (1L << (1 << i)) - 1;
for (int j = numFilteredMasks; j < masks.Length; j++)
{
if ((masks[j] & filter) != 0)
{
var tmp = masks[j];
masks[j] = masks[numFilteredMasks];
masks[numFilteredMasks++] = tmp;
}
}
// Search for a successful assignment, using the information from the previous search to skip a few initial values in this one.
set |= (1L << numFilteredMasks) - 1 - M;
M = (1L << numFilteredMasks) - 1;
while (true)
{
if (TestAssignment(periods, i, ones, masks, set)) break;
if (set == 0) return false; // No suitable assignment found
// Gosper's hack with variant to reduce the number of bits on overflow
long c = set & -set;
long r = set + c;
set = (((r ^ set) >> 2) / c) | (r & M);
}
}
return true;
}
private static bool TestAssignment(int[] periods, int idx, long ones, long[] masks, long assignment)
{
for (int j = 0; j < masks.Length; j++, assignment >>= 1) ones |= masks[j] & -(assignment & 1);
for (int i = idx - 1; i > 0; i--) // i == 0 is already handled in the unification process.
{
if (Period(ones, 2 << i, periods[i - 1]) < periods[i]) return false;
}
return true;
}
private static int Period(long arr, int n, int min)
{
for (int p = min; p <= n; p++)
{
// If the bottom n bits have period p then the bottom (n-p) bits equal the bottom (n-p) bits of the integer shifted right p
long mask = (1L << (n - p)) - 1L;
if ((arr & mask) == ((arr >> p) & mask)) return p;
}
throw new Exception("Unreachable");
}
class UnionSet
{
private int[] _Lookup;
public UnionSet(int size)
{
_Lookup = new int[size];
for (int k = 0; k < size; k++) _Lookup[k] = k;
}
public int Find(int key)
{
var l = _Lookup[key];
if (l != key) _Lookup[key] = l = Find(l);
return l;
}
public void Unify(int key1, int key2)
{
int root1 = Find(key1);
int root2 = Find(key2);
if (root1 < root2) _Lookup[root2] = root1;
else _Lookup[root1] = root2;
}
}
}
}
Das Erweitern auf n = 256 würde das Wechseln zu BigInteger
für die Masken erfordern , was die Leistung wahrscheinlich zu sehr beeinträchtigt, als dass n = 128 ohne neue Ideen durchgehen könnte, geschweige denn n = 256.
Kompilieren Sie unter Linux mit mono-csc
und führen Sie mit aus mono
.
Grundlegende Erklärung
Ich werde keine zeilenweise Dissektion durchführen, sondern nur einen Überblick über die Konzepte.
Als Faustregel gehe ich gerne in der Größenordnung von 2 50 Elementen in einem kombinatorischen Brute-Force-Programm durch. Um zu n = 128 zu gelangen, muss daher ein Ansatz verwendet werden, der nicht jede Bitfolge analysiert. Anstatt von Bitfolgen zu Periodensequenzen vorwärts zu arbeiten, arbeite ich rückwärts: Gibt es bei einer Periodensequenz eine Bitfolge, die dies realisiert? Für n = 2 x gibt es eine einfache Obergrenze von 2 x (x + 1) / 2 Periodensequenzen (vs 2 2 x Bitstrings).
Einige der Argumente verwenden das String-Periodizitäts-Lemma :
Sei p
und q
sei zwei Perioden einer Länge n
. Wenn p + q ≤ n + gcd(p, q)
dann gcd(p, q)
ist auch ein Punkt der Zeichenfolge.
Wlog Ich gehe davon aus, dass alle betrachteten Bitstrings mit beginnen 0
.
Bei einer Periodenfolge, in der die Periode des Präfixes der Länge 2 i ( immer) angegeben ist, gibt es einige einfache Beobachtungen zu möglichen Werten von :[p1 p2 ... pk]
pi
p0 = 1
pk+1
pk+1 ≥ pk
da eine Periode eines Strings S
auch eine Periode eines beliebigen Präfixes von ist S
.
pk+1 = pk
ist immer eine mögliche Erweiterung: Wiederholen Sie einfach dieselbe primitive Zeichenfolge für doppelt so viele Zeichen.
2k < pk+1 ≤ 2k+1
ist immer eine mögliche Erweiterung. Es reicht aus, dies zu zeigen, da eine aperiodische Zeichenfolge mit einer Länge zu einer aperiodischen Zeichenfolge mit Länge erweitert werden kann, indem ein Buchstabe angehängt wird, der nicht der erste Buchstabe ist.pk+1 = 2k+1
L
L+1
Nehmen Sie eine Zeichenfolge mit Sx
einer Länge von 2 k, deren Periode ist, und betrachten Sie die Zeichenfolge mit einer Länge von 2 k + 1 . Offensichtlich hat eine Periode 2 k +1. Angenommen, seine Periode ist kleiner.pk
SxyS
SxyS
q
Dann ist also durch die Periodizität das Lemma auch eine Periode von , und da der größte Teiler kleiner oder gleich seinen Argumenten ist und die kleinste Periode ist, müssen wir ein geeigneter Faktor von 2 k + 1 sein. Da sein Quotient nicht 2 sein kann, haben wir .2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q)
gcd(2k+1, q)
SxyS
q
q
q ≤ (2k+1)/3
Nun, da ist eine Periode davon muss eine Periode von sein . Aber die Zeit von ist . Wir haben zwei Fälle:q ≤ 2k
SxyS
Sx
Sx
pk
gcd(pk, q) = pk
oder gleichwertig genau in .pk
q
pk + q > 2k + gcd(pk, q)
damit das Periodizitäts-Lemma keine kleinere Periode erzwingt.
Betrachten Sie zunächst den zweiten Fall. , im Widerspruch zur Definition von als der Zeitraum von . Deshalb sind wir zu dem Schluss gezwungen, dass dies ein Faktor ist .pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2q
pk
Sx
pk
q
Da es sich jedoch q
um eine Periode von Sx
und um die Periode von handelt , ist das Präfix der Länge nur eine Kopie des Präfixes der Länge , sodass wir sehen, dass dies auch eine Periode von ist .pk
Sx
q
q/pk
pk
pk
SxyS
Daher ist die Periode von SxyS
entweder oder 2 k + 1. Wir haben aber zwei Möglichkeiten für ! Höchstens eine Wahl von ergibt eine Periode , so dass mindestens eine Periode 2 k + 1 ergibt . QED.pk
y
y
pk
Das Periodizitäts-Lemma erlaubt es uns, einige der verbleibenden möglichen Erweiterungen abzulehnen.
Jede Erweiterung, die keinen Quick-Accept- oder Quick-Reject-Test bestanden hat, muss konstruktiv getestet werden.
Die Konstruktion eines Bitstrings bei gegebener Periodenfolge ist im Wesentlichen ein Problem der Erfüllbarkeit, hat jedoch eine große Struktur. Es gibt einfache Gleichheitsbeschränkungen, die von jeder Präfixperiode impliziert werden, daher verwende ich eine Union-Set- Datenstruktur, um Bits zu unabhängigen Clustern zu kombinieren. Dies war genug, um n = 64 anzugehen, aber für n = 128 war es notwendig, weiter zu gehen. Ich verwende zwei nützliche Argumentationslinien:2k - pk
- Wenn das Präfix der Länge
M
ist und das Präfix der Länge einen Punkt hat, muss das Präfix der Länge mit enden . Dies ist gerade in den Fällen am wirkungsvollsten, in denen sonst die meisten unabhängigen Cluster vorhanden wären, was praktisch ist.01M-1
L > M
L
L
1M
- Wenn das Präfix der Länge
M
ist und das Präfix der Länge einen Punkt mit hat und mit endet , muss es tatsächlich mit enden . Dies ist im entgegengesetzten Extrem am stärksten, wenn die Periodensequenz mit vielen beginnt.0M
L > M
L - d
d < M
0d
10d
Wenn wir keinen unmittelbaren Widerspruch erhalten, indem wir den Cluster mit dem ersten Bit (angenommen Null) als Eins erzwingen, erzwingen wir (mit einigen Mikrooptimierungen) die möglichen Werte für die ungezwungenen Cluster. Beachten Sie, dass die Reihenfolge in absteigender Anzahl von Einsen ist, denn wenn das i
th- Bit ein Eins ist, kann die Periode nicht sein, i
und wir möchten Perioden vermeiden, die kürzer sind als diejenigen, die bereits durch das Clustering erzwungen werden. Ein Abstieg erhöht die Wahrscheinlichkeit, frühzeitig einen gültigen Auftrag zu finden.