C#、約2:40でn = 128
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 = 256はもちろんのこと、n = 128が新しいアイデアなしで通過するにはパフォーマンスが大幅に低下する可能性があります。
Linuxでは、でコンパイルしmono-csc
て実行しmono
ます。
基本的な説明
行ごとの分析は行わず、概念の概要のみを説明します。
経験則として、ブルートフォースの組み合わせプログラムで2〜50個の要素を繰り返し処理できます。したがって、n = 128に到達するには、すべてのビット列を分析しないアプローチを使用する必要があります。ですから、ビット列から周期シーケンスに進むのではなく、逆に動作します:周期シーケンスが与えられた場合、それを実現するビット文字列はありますか?n = 2 xの場合、2 x(x + 1)/ 2周期シーケンスの簡単な上限があります(vs 2 2 xビット文字列)。
引数のいくつかは、文字列の周期性補題を使用します:
させるp
およびq
の長さの文字列の2つの期間です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
は常に可能な拡張です。同じプリミティブ文字列を2倍の文字数だけ繰り返します。
2k < pk+1 ≤ 2k+1
常に可能な拡張です。これは、最初の文字ではない任意の文字を追加することで、非周期的な長さの文字列を非周期的な長さの文字列に拡張できるため、これを表示するだけで十分です。pk+1 = 2k+1
L
L+1
周期がSx
の長さ2 kのストリングを取り、長さ2 k + 1のストリングを検討します。明らかに2 k +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
さて、の期間はの期間でなければなりません。しかし、期間はです。次の2つのケースがあります。q ≤ 2k
SxyS
Sx
Sx
pk
gcd(pk, q) = pk
、または同等に正確にに分割します。pk
q
pk + q > 2k + gcd(pk, q)
周期性補題がより短い期間を強制しないように。
最初に2番目のケースを検討してください。の期間としての定義に矛盾しています。したがって、私たちは要因である結論に強制されます。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つのオプションがあります!最大で1つの選択肢から期間が与えられるため、少なくとも1 つの選択肢で期間2 k +1 が与えられます。QED。pk
y
y
pk
周期性補題により、残りの拡張の一部を拒否できます。
クイック受け入れテストまたはクイック拒否テストに合格していない拡張機能は、建設的にテストする必要があります。
周期シーケンスを指定したビット文字列の構築は、本質的に充足可能性の問題ですが、多くの構造を持っています。各プレフィックス期間によって暗示される単純な等式制約があるため、ユニオンセットデータ構造を使用して、ビットを独立したクラスターに結合します。これはn = 64に取り組むには十分でしたが、n = 128の場合はさらに先へ進む必要がありました。私は2つの有用な議論の行を採用しています:2k - pk
- 長さのプレフィックスがいる場合
M
であると長さのプレフィックスが期間を持って、その後長さの接頭辞がで終わらなければなりません。これは、そうでなければ最も独立したクラスターを持つ場合に最も正確であり、便利です。01M-1
L > M
L
L
1M
- 長さのプレフィックスは場合
M
ですと長さの接頭辞は、期間があるとして、終了する、それが実際の最後にでなければなりません。これは、ピリオドシーケンスが多くのピリオドで始まる場合の反対の極端な場合に最も強力です。0M
L > M
L - d
d < M
0d
10d
クラスターを最初のビット(ゼロと仮定)を1に強制することで即座に矛盾が生じない場合、強制されていないクラスターの可能な値に対してブルートフォース(いくつかのミクロ最適化)を行います。順序は1の降順であることに注意してくださいi
。thビットが1の場合、期間はできずi
、クラスタリングによって既に実施されている期間よりも短い期間を避けたいためです。下がると、有効な課題を早期に見つける可能性が高くなります。