C #, n = 128 примерно в 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;
}
}
}
}
Расширение до n = 256 потребовало бы переключения BigInteger
на маски, что, вероятно, слишком сильно снижает производительность, чтобы n = 128 мог пройти без новых идей, не говоря уже о n = 256.
Под Linux скомпилируйте mono-csc
и выполните с mono
.
Основное объяснение
Я не собираюсь делать построчное рассечение, просто обзор концепций.
Как правило, я рад перебрать порядка 2 50 элементов в комбинаторной программе грубой силы. Поэтому, чтобы получить n = 128, необходимо использовать подход, который не анализирует каждую цепочку битов. Таким образом, вместо того чтобы работать вперед от цепочек битов к последовательностям периодов, я работаю задом наперед: есть ли последовательность периодов, есть ли цепочка битов, которая ее реализует? Для n = 2 x существует простая верхняя граница 2 x (x + 1) / 2 последовательностей периодов (против 2 2 x цепочек битов).
Некоторые аргументы используют лемму периодичности строки :
Позвольте p
и q
быть два периода строки длины n
. Если p + q ≤ n + gcd(p, q)
то gcd(p, q)
это также период строки.
Wlog Я предполагаю, что все рассматриваемые цепочки битов начинаются с 0
.
Учитывая последовательность периодов, где есть период префикса длины 2 i ( всегда), есть несколько простых наблюдений о возможных значениях :[p1 p2 ... pk]
pi
p0 = 1
pk+1
pk+1 ≥ pk
так как период строки S
также является периодом любого префикса S
.
pk+1 = pk
всегда возможное расширение: просто повторите одну и ту же примитивную строку для вдвое большего количества символов.
2k < pk+1 ≤ 2k+1
всегда возможное расширение. Достаточно показать это, потому что апериодическая строка длины может быть расширена до апериодической строки длиныpk+1 = 2k+1
L
L+1
, добавляя любую букву, которая не является ее первой буквой.
Возьмем строку Sx
длиной 2 k , период которой равен, и рассмотрим строку длиной 2 k + 1 . Ясно , что есть в период 2 к +1. Предположим, его период меньше.pk
SxyS
SxyS
q
Тогда по лемме о периодичности также есть период , и, поскольку наибольший делитель меньше или равен своим аргументам и является наименьшим периодом, мы требуем, чтобы коэффициент составлял 2 k +1. Так как его частное не может быть 2, мы имеем .2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q)
gcd(2k+1, q)
SxyS
q
q
q ≤ (2k+1)/3
Теперь, поскольку это период, это должен быть период . Но период является . У нас есть два случая:q ≤ 2k
SxyS
Sx
Sx
pk
gcd(pk, q) = pk
или эквивалентно делится точно на .pk
q
pk + q > 2k + gcd(pk, q)
так что лемма периодичности не форсирует меньший период.
Рассмотрим сначала второй случай. , противореча определению как период . Поэтому мы вынуждены сделать вывод, что это фактор .pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2q
pk
Sx
pk
q
Но поскольку q
это период Sx
и является периодом , префикс длины - это просто копии префикса длины , поэтому мы видим, что это также период .pk
Sx
q
q/pk
pk
pk
SxyS
Следовательно, период SxyS
равен или 2 k +1. Но у нас есть два варианта ! Максимум один выбор даст период , поэтому по крайней мере один даст период 2 k +1. QED.pk
y
y
pk
Лемма периодичности позволяет отбросить некоторые из оставшихся возможных расширений.
Любое расширение, которое не прошло тест быстрого принятия или быстрого отклонения, нуждается в конструктивной проверке.
Построение цепочки битов с заданной последовательностью периодов является по существу проблемой удовлетворительности, но она имеет много структур. Существуют простые ограничения равенства, подразумеваемые каждым периодом префикса, поэтому я использую структуру данных объединенного набора для объединения битов в независимые кластеры. Этого было достаточно, чтобы решить n = 64, но для n = 128 нужно было идти дальше. Я использую две полезные аргументы:2k - pk
- Если префикс длины
M
равен, а префикс длины имеет период, тогда префикс длины должен заканчиваться на . Это наиболее эффективно именно в тех случаях, когда в противном случае было бы большинство независимых кластеров, что удобно.01M-1
L > M
L
L
1M
- Если префикс длины
M
равен, а префикс длины имеет период с и заканчивается, то фактически он должен заканчиваться на . Это наиболее эффективно в противоположной крайности, когда последовательность периодов начинается с множества единиц.0M
L > M
L - d
d < M
0d
10d
Если мы не получим непосредственное противоречие, заставив кластер с первым битом (предположительно равным нулю) равным единице, то мы перебираем силу (с некоторыми микрооптимизациями) над возможными значениями для не принудительных кластеров. Обратите внимание, что в порядке убывания числа единиц, потому что, если i
th-й бит равен единице, период не может быть, i
и мы хотим избежать периодов, которые короче тех, которые уже применяются кластеризацией. Понижение повышает шансы найти действительное назначение на ранней стадии.