C #, n = 128 w około 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;
}
}
}
}
Rozszerzenie do n = 256 wymagałoby przełączenia BigInteger
na maski, co prawdopodobnie zabija wydajność zbyt mocno, aby n = 128 mógł przejść bez nowych pomysłów, nie mówiąc już o n = 256.
Pod Linuksem kompiluj mono-csc
i wykonuj za pomocą mono
.
Podstawowe wyjaśnienie
Nie zamierzam dokonywać szczegółowej analizy, tylko przegląd pojęć.
Zasadniczo chętnie powtarzam w kolejności 2 50 elementów w programie kombinatorycznym typu brute-force. Aby dostać się do n = 128, konieczne jest zatem zastosowanie podejścia, które nie analizuje każdego ciągu bitów. Więc zamiast pracować naprzód od ciągów bitów do sekwencji kropek, pracuję wstecz: czy biorąc pod uwagę sekwencję kropek, czy istnieje ciąg bitów, który to realizuje? Dla n = 2 x jest łatwa górna granica 2 x (x + 1) / 2 sekwencji okresowych (w porównaniu do 2 2 x ciągów bitów).
Niektóre argumenty używają lematu okresowości łańcucha :
Niech p
i q
dwa okresy ciąg długości n
. Jeśli p + q ≤ n + gcd(p, q)
wtedy gcd(p, q)
jest także kropka ciągu.
Wlog Zakładam, że wszystkie rozważane ciągi bitów zaczynają się od 0
.
Biorąc pod uwagę sekwencję okresów, w której występuje okres prefiksu o długości 2 i ( zawsze), istnieją pewne łatwe obserwacje dotyczące możliwych wartości :[p1 p2 ... pk]
pi
p0 = 1
pk+1
pk+1 ≥ pk
ponieważ okres ciągu znaków S
jest również okresem dowolnego prefiksu S
.
pk+1 = pk
jest zawsze możliwym rozszerzeniem: wystarczy powtórzyć ten sam prymitywny ciąg dla dwóch razy większej liczby znaków.
2k < pk+1 ≤ 2k+1
jest zawsze możliwym rozszerzeniem. Wystarczy to wykazać, ponieważ aperiodyczny ciąg długości można przedłużyć do aperiodycznego ciągu długości , dodając dowolną literę, która nie jest jego pierwszą literą.pk+1 = 2k+1
L
L+1
Weź łańcuch Sx
o długości 2 k, którego okres jest, i rozważ łańcuch o długości 2 k + 1 . Wyraźnie ma na okres 2 k +1. Załóżmy, że jego okres jest krótszy.pk
SxyS
SxyS
q
Zatem przez okresowość lemat jest także okresem , a ponieważ największy dzielnik jest mniejszy lub równy swoim argumentom i jest najmniejszym okresem, musimy mieć odpowiedni współczynnik 2 k +1. Ponieważ jego iloraz nie może wynosić 2, mamy .2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q)
gcd(2k+1, q)
SxyS
q
q
q ≤ (2k+1)/3
Teraz, ponieważ jest to okres, który musi być okresem . Ale okres jest . Mamy dwa przypadki:q ≤ 2k
SxyS
Sx
Sx
pk
gcd(pk, q) = pk
lub równo dzieli się na .pk
q
pk + q > 2k + gcd(pk, q)
aby lemat okresowości nie wymuszał krótszego okresu.
Rozważ najpierw drugi przypadek. , co jest sprzeczne z definicją okresu . Dlatego jesteśmy zmuszeni dojść do wniosku, że jest to czynnik .pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2q
pk
Sx
pk
q
Ale ponieważ q
jest to okres Sx
i jest to okres , prefiks długości jest tylko kopią prefiksu długości , więc widzimy, że jest to również okres .pk
Sx
q
q/pk
pk
pk
SxyS
Dlatego okres SxyS
wynosi albo 2 k +1. Ale mamy dwie opcje ! Co najwyżej jeden wybór da okres , więc co najmniej jeden da okres 2 k +1. CO BYŁO DO OKAZANIA.pk
y
y
pk
Lemat okresowości pozwala nam odrzucić niektóre z pozostałych możliwych rozszerzeń.
Każde rozszerzenie, które nie przeszło testu szybkiego akceptowania lub szybkiego odrzucania, musi zostać przetestowane konstruktywnie.
Konstrukcja łańcucha bitów w sekwencji okresu jest zasadniczo problemem satysfakcji, ale ma wiele struktur. Istnieją proste ograniczenia równości implikowane przez każdy okres prefiksu, więc używam struktury danych zbioru unii do łączenia bitów w niezależne klastry. To wystarczyło, aby pokonać n = 64, ale dla n = 128 konieczne było pójście dalej. Używam dwóch użytecznych argumentów:2k - pk
- Jeśli prefiksem długości
M
jest i prefiks długości ma kropkę, to prefiks długości musi kończyć się na . Jest to najsilniejsze właśnie w przypadkach, które w innym przypadku miałyby najbardziej niezależne klastry, co jest wygodne.01M-1
L > M
L
L
1M
- Jeśli prefiks długości
M
jest, a prefiks długości ma kropkę z i kończy się w, to tak naprawdę musi on kończyć się na . Jest to najsilniejsze w przeciwnej skrajności, gdy sekwencja okresu zaczyna się od wielu.0M
L > M
L - d
d < M
0d
10d
Jeśli nie otrzymamy natychmiastowej sprzeczności, zmuszając klaster z pierwszym bitem (zakładanym jako zero) do jednego, wówczas brutalnie działamy (z pewnymi mikrooptymalizacjami) nad możliwymi wartościami dla niewymuszonych klastrów. Zauważ, że kolejność jest w malejącej liczby tych, bo jeśli i
ty bit jest jeden, to okres ten nie może być i
i chcemy uniknąć okresów, które są krótsze niż te, które są już egzekwowane przez klastry. Zejście w dół zwiększa szanse na wcześniejsze znalezienie ważnego zadania.