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 BigIntegerna 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-csci 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 pi qdwa 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]pip0 = 1pk+1
pk+1 ≥ pkponieważ okres ciągu znaków Sjest 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+1jest 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+1LL+1
Weź łańcuch Sxo 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.pkSxySSxySq
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)SxySqqq ≤ (2k+1)/3
Teraz, ponieważ jest to okres, który musi być okresem . Ale okres jest . Mamy dwa przypadki:q ≤ 2kSxySSxSxpk
gcd(pk, q) = pklub równo dzieli się na .pkq
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 ≥ 2qpkSxpkq
Ale ponieważ qjest to okres Sxi jest to okres , prefiks długości jest tylko kopią prefiksu długości , więc widzimy, że jest to również okres .pkSxqq/pkpkpkSxyS
Dlatego okres SxySwynosi 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.pkyypk
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
Mjest 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-1L > MLL1M
- Jeśli prefiks długości
Mjest, 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.0ML > ML - dd < M0d10d
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 ity bit jest jeden, to okres ten nie może być ii 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.