24/13 26/14 28/15 30/16 32/17(C#)
編集:
回答から古い情報を削除しました。私は、Peter Taylorとほぼ同じアルゴリズムを使用しています(編集:彼は現在、より優れたアルゴリズムを使用しているようです)が、独自の最適化をいくつか追加しました。
- 同じベクトルの合計(このKennyTMのコメントで提案されている)で列セットを検索する「中間一致」戦略を実装しました。この戦略はメモリ使用量を大幅に改善しましたが、かなり遅いので
HasPropertyXFast
、「meet in the middle」アプローチを使用する前に等しい合計の小さなセットがあるかどうかをすばやくチェックする関数を追加しました。
HasPropertyXFast
関数内の列セットを反復処理しながら、1列の列セットをチェックしてから、2、3などの列セットをチェックします。この関数は、列の合計の最初の衝突が見つかるとすぐに戻ります。実際には、通常、数百万ではなく数百または数千の列セットのみをチェックする必要があります。
long
変数を使用して、列全体とそのベクトルの合計を保存および比較しています。このアプローチは、列を配列として比較するよりも少なくとも1桁高速です。
- ハッシュセットの独自の実装を追加しました。これは、
long
データ型と使用パターンに最適化されています。
- メモリ割り当ての数を減らしてパフォーマンスを向上させるために、アプリケーションの存続期間全体にわたって同じ3つのハッシュセットを再利用しています。
- マルチスレッドのサポート。
プログラム出力:
00000000000111011101010010011111
10000000000011101110101001001111
11000000000001110111010100100111
11100000000000111011101010010011
11110000000000011101110101001001
11111000000000001110111010100100
01111100000000000111011101010010
00111110000000000011101110101001
10011111000000000001110111010100
01001111100000000000111011101010
00100111110000000000011101110101
10010011111000000000001110111010
01001001111100000000000111011101
10100100111110000000000011101110
01010010011111000000000001110111
10101001001111100000000000111011
11010100100111110000000000011101
Score: 32/17 = 1,88235294117647
Time elapsed: 02:11:05.9791250
コード:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
class Program
{
const int MaxWidth = 32;
const int MaxHeight = 17;
static object _lyndonWordLock = new object();
static void Main(string[] args)
{
Stopwatch sw = Stopwatch.StartNew();
double maxScore = 0;
const int minHeight = 17; // 1
for (int height = minHeight; height <= MaxHeight; height++)
{
Console.WriteLine("Row count = " + height);
Console.WriteLine("Time elapsed: " + sw.Elapsed + "\r\n");
int minWidth = Math.Max(height, (int)(height * maxScore) + 1);
for (int width = minWidth; width <= MaxWidth; width++)
{
#if MULTITHREADING
int[,] matrix = FindMatrixParallel(width, height);
#else
int[,] matrix = FindMatrix(width, height);
#endif
if (matrix != null)
{
PrintMatrix(matrix);
Console.WriteLine("Time elapsed: " + sw.Elapsed + "\r\n");
maxScore = (double)width / height;
}
else
break;
}
}
}
#if MULTITHREADING
static int[,] FindMatrixParallel(int width, int height)
{
_lyndonWord = 0;
_stopSearch = false;
int threadCount = Environment.ProcessorCount;
Task<int[,]>[] tasks = new Task<int[,]>[threadCount];
for (int i = 0; i < threadCount; i++)
tasks[i] = Task<int[,]>.Run(() => FindMatrix(width, height));
int index = Task.WaitAny(tasks);
if (tasks[index].Result != null)
_stopSearch = true;
Task.WaitAll(tasks);
foreach (Task<int[,]> task in tasks)
if (task.Result != null)
return task.Result;
return null;
}
static volatile bool _stopSearch;
#endif
static int[,] FindMatrix(int width, int height)
{
#if MULTITHREADING
_columnSums = new LongSet();
_left = new LongSet();
_right = new LongSet();
#endif
foreach (long rowTemplate in GetLyndonWords(width))
{
int[,] matrix = new int[width, height];
for (int x = 0; x < width; x++)
{
int cellValue = (int)(rowTemplate >> (width - 1 - x)) % 2;
for (int y = 0; y < height; y++)
matrix[(x + y) % width, y] = cellValue;
}
if (!HasPropertyX(matrix))
return matrix;
#if MULTITHREADING
if (_stopSearch)
return null;
#endif
}
return null;
}
#if MULTITHREADING
static long _lyndonWord;
#endif
static IEnumerable<long> GetLyndonWords(int length)
{
long lyndonWord = 0;
long max = (1L << (length - 1)) - 1;
while (lyndonWord <= max)
{
if ((lyndonWord % 2 != 0) && PrecedesReversal(lyndonWord, length))
yield return lyndonWord;
#if MULTITHREADING
lock (_lyndonWordLock)
{
if (_lyndonWord <= max)
_lyndonWord = NextLyndonWord(_lyndonWord, length);
else
yield break;
lyndonWord = _lyndonWord;
}
#else
lyndonWord = NextLyndonWord(lyndonWord, length);
#endif
}
}
static readonly int[] _lookup =
{
32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17,
0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18
};
static int NumberOfTrailingZeros(uint i)
{
return _lookup[(i & -i) % 37];
}
static long NextLyndonWord(long w, int length)
{
if (w == 0)
return 1;
int currentLength = length - NumberOfTrailingZeros((uint)w);
while (currentLength < length)
{
w += w >> currentLength;
currentLength *= 2;
}
w++;
return w;
}
private static bool PrecedesReversal(long lyndonWord, int length)
{
int shift = length - 1;
long reverse = 0;
for (int i = 0; i < length; i++)
{
long bit = (lyndonWord >> i) % 2;
reverse |= bit << (shift - i);
}
for (int i = 0; i < length; i++)
{
if (reverse < lyndonWord)
return false;
long bit = reverse % 2;
reverse /= 2;
reverse += bit << shift;
}
return true;
}
#if MULTITHREADING
[ThreadStatic]
#endif
static LongSet _left = new LongSet();
#if MULTITHREADING
[ThreadStatic]
#endif
static LongSet _right = new LongSet();
static bool HasPropertyX(int[,] matrix)
{
long[] matrixColumns = GetMatrixColumns(matrix);
if (matrixColumns.Length == 1)
return false;
return HasPropertyXFast(matrixColumns) || MeetInTheMiddle(matrixColumns);
}
static bool MeetInTheMiddle(long[] matrixColumns)
{
long[] leftColumns = matrixColumns.Take(matrixColumns.Length / 2).ToArray();
long[] rightColumns = matrixColumns.Skip(matrixColumns.Length / 2).ToArray();
if (PrepareHashSet(leftColumns, _left) || PrepareHashSet(rightColumns, _right))
return true;
foreach (long columnSum in _left.GetValues())
if (_right.Contains(columnSum))
return true;
return false;
}
static bool PrepareHashSet(long[] columns, LongSet sums)
{
int setSize = (int)System.Numerics.BigInteger.Pow(3, columns.Length);
sums.Reset(setSize, setSize);
foreach (long column in columns)
{
foreach (long sum in sums.GetValues())
if (!sums.Add(sum + column) || !sums.Add(sum - column))
return true;
if (!sums.Add(column) || !sums.Add(-column))
return true;
}
return false;
}
#if MULTITHREADING
[ThreadStatic]
#endif
static LongSet _columnSums = new LongSet();
static bool HasPropertyXFast(long[] matrixColumns)
{
int width = matrixColumns.Length;
int maxColumnCount = width / 3;
_columnSums.Reset(width, SumOfBinomialCoefficients(width, maxColumnCount));
int resetBit, setBit;
for (int k = 1; k <= maxColumnCount; k++)
{
uint columnMask = (1u << k) - 1;
long sum = 0;
for (int i = 0; i < k; i++)
sum += matrixColumns[i];
while (true)
{
if (!_columnSums.Add(sum))
return true;
if (!NextColumnMask(columnMask, k, width, out resetBit, out setBit))
break;
columnMask ^= (1u << resetBit) ^ (1u << setBit);
sum = sum - matrixColumns[resetBit] + matrixColumns[setBit];
}
}
return false;
}
// stolen from Peter Taylor
static bool NextColumnMask(uint mask, int k, int n, out int resetBit, out int setBit)
{
int gap = NumberOfTrailingZeros(~mask);
int next = 1 + NumberOfTrailingZeros(mask & (mask + 1));
if (((k - gap) & 1) == 0)
{
if (gap == 0)
{
resetBit = next - 1;
setBit = next - 2;
}
else if (gap == 1)
{
resetBit = 0;
setBit = 1;
}
else
{
resetBit = gap - 2;
setBit = gap;
}
}
else
{
if (next == n)
{
resetBit = 0;
setBit = 0;
return false;
}
if ((mask & (1 << next)) == 0)
{
if (gap == 0)
{
resetBit = next - 1;
setBit = next;
}
else
{
resetBit = gap - 1;
setBit = next;
}
}
else
{
resetBit = next;
setBit = gap;
}
}
return true;
}
static long[] GetMatrixColumns(int[,] matrix)
{
int width = matrix.GetLength(0);
int height = matrix.GetLength(1);
long[] result = new long[width];
for (int x = 0; x < width; x++)
{
long column = 0;
for (int y = 0; y < height; y++)
{
column *= 13;
if (matrix[x, y] == 1)
column++;
}
result[x] = column;
}
return result;
}
static int SumOfBinomialCoefficients(int n, int k)
{
int result = 0;
for (int i = 0; i <= k; i++)
result += BinomialCoefficient(n, i);
return result;
}
static int BinomialCoefficient(int n, int k)
{
long result = 1;
for (int i = n - k + 1; i <= n; i++)
result *= i;
for (int i = 2; i <= k; i++)
result /= i;
return (int)result;
}
static void PrintMatrix(int[,] matrix)
{
int width = matrix.GetLength(0);
int height = matrix.GetLength(1);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
Console.Write(matrix[x, y]);
Console.WriteLine();
}
Console.WriteLine("Score: {0}/{1} = {2}", width, height, (double)width / height);
}
}
class LongSet
{
private static readonly int[] primes =
{
17, 37, 67, 89, 113, 149, 191, 239, 307, 389, 487, 613, 769, 967, 1213, 1523, 1907,
2389, 2999, 3761, 4703, 5879, 7349, 9187, 11489, 14369, 17971, 22469, 28087, 35111,
43889, 54869, 68597, 85751, 107197, 133999, 167521, 209431, 261791, 327247, 409063,
511333, 639167, 798961, 998717, 1248407, 1560511, 1950643, 2438309, 3047909,
809891, 4762367, 5952959, 7441219, 9301529, 11626913, 14533661, 18167089, 22708867,
28386089, 35482627, 44353297, 55441637, 69302071, 86627603, 108284507, 135355669,
169194593, 211493263, 264366593, 330458263, 413072843, 516341057, 645426329,
806782913, 1008478649, 1260598321
};
private int[] _buckets;
private int[] _nextItemIndexes;
private long[] _items;
private int _count;
private int _minCapacity;
private int _maxCapacity;
private int _currentCapacity;
public LongSet()
{
Initialize(0, 0);
}
private int GetPrime(int capacity)
{
foreach (int prime in primes)
if (prime >= capacity)
return prime;
return int.MaxValue;
}
public void Reset(int minCapacity, int maxCapacity)
{
if (maxCapacity > _maxCapacity)
Initialize(minCapacity, maxCapacity);
else
ClearBuckets();
}
private void Initialize(int minCapacity, int maxCapacity)
{
_minCapacity = GetPrime(minCapacity);
_maxCapacity = GetPrime(maxCapacity);
_currentCapacity = _minCapacity;
_buckets = new int[_maxCapacity];
_nextItemIndexes = new int[_maxCapacity];
_items = new long[_maxCapacity];
_count = 0;
}
private void ClearBuckets()
{
Array.Clear(_buckets, 0, _currentCapacity);
_count = 0;
_currentCapacity = _minCapacity;
}
public bool Add(long value)
{
int bucket = (int)((ulong)value % (ulong)_currentCapacity);
for (int i = _buckets[bucket] - 1; i >= 0; i = _nextItemIndexes[i])
if (_items[i] == value)
return false;
if (_count == _currentCapacity)
{
Grow();
bucket = (int)((ulong)value % (ulong)_currentCapacity);
}
int index = _count;
_items[index] = value;
_nextItemIndexes[index] = _buckets[bucket] - 1;
_buckets[bucket] = index + 1;
_count++;
return true;
}
private void Grow()
{
Array.Clear(_buckets, 0, _currentCapacity);
const int growthFactor = 8;
int newCapacity = GetPrime(_currentCapacity * growthFactor);
if (newCapacity > _maxCapacity)
newCapacity = _maxCapacity;
_currentCapacity = newCapacity;
for (int i = 0; i < _count; i++)
{
int bucket = (int)((ulong)_items[i] % (ulong)newCapacity);
_nextItemIndexes[i] = _buckets[bucket] - 1;
_buckets[bucket] = i + 1;
}
}
public bool Contains(long value)
{
int bucket = (int)((ulong)value % (ulong)_buckets.Length);
for (int i = _buckets[bucket] - 1; i >= 0; i = _nextItemIndexes[i])
if (_items[i] == value)
return true;
return false;
}
public IReadOnlyList<long> GetValues()
{
return new ArraySegment<long>(_items, 0, _count);
}
}
構成ファイル:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<gcAllowVeryLargeObjects enabled="true" />
</runtime>
</configuration>