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 + 1) / 2 (проти 2 2- х біт-рядків).
У деяких аргументах використовується лемма рядкової періодичності :
Нехай 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 к +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
і ми хочемо уникати періодів, коротших, ніж ті, які вже виконуються кластеризацією. Зниження збільшує шанси на те, щоб знайти дійсне завдання завчасно.