C ++ (heuristik): 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086
Ini sedikit di belakang hasil Peter Taylor, menjadi 1 sampai 3 lebih rendah untuk n=7
, 9
dan 10
. Keuntungannya adalah lebih cepat, jadi saya bisa menjalankannya untuk nilai yang lebih tinggi n
. Dan itu bisa dipahami tanpa matematika mewah. ;)
Kode saat ini dibuat untuk menjalankan hingga n=15
. Jalankan kali meningkat kira-kira dengan faktor 4 untuk setiap peningkatan n
. Misalnya, 0,008 detik untuk n=7
, 0,07 detik untuk n=9
, 1,34 detik untuk n=11
, 27 detik untuk n=13
, dan 9 menit untuk n=15
.
Ada dua pengamatan utama yang saya gunakan:
- Alih-alih beroperasi pada nilai-nilai itu sendiri, heuristik beroperasi pada penghitungan array. Untuk melakukan ini, daftar semua array penghitungan unik dihasilkan terlebih dahulu.
- Menggunakan array penghitungan dengan nilai-nilai kecil lebih menguntungkan, karena mereka menghilangkan lebih sedikit ruang solusi. Ini didasarkan pada setiap penghitungan
c
tidak termasuk rentang c / 2
hingga 2 * c
dari nilai lain. Untuk nilai yang lebih kecil c
, rentang ini lebih kecil, yang berarti bahwa nilai yang lebih sedikit dikecualikan dengan menggunakannya.
Hasilkan Array Penghitungan Unik
Saya menggunakan brute force yang satu ini, mengulangi semua nilai, menghasilkan array jumlah untuk masing-masing, dan menyatukan daftar yang dihasilkan. Ini tentu saja dapat dilakukan dengan lebih efisien, tetapi cukup baik untuk jenis nilai yang kami operasikan.
Ini sangat cepat untuk nilai-nilai kecil. Untuk nilai yang lebih besar, biaya overhead menjadi substansial. Misalnya, untuk n=15
, ia menggunakan sekitar 75% dari keseluruhan runtime. Ini pasti akan menjadi area untuk dilihat ketika mencoba untuk pergi jauh lebih tinggi daripada n=15
. Bahkan penggunaan memori untuk membangun daftar array penghitungan untuk semua nilai akan mulai menjadi masalah.
Jumlah array penghitungan unik adalah sekitar 6% dari jumlah nilai untuk n=15
. Jumlah relatif ini menjadi lebih kecil karena n
menjadi lebih besar.
Pilihan Serakah Menghitung Nilai Array
Bagian utama dari algoritma memilih penghitungan nilai array dari daftar yang dihasilkan menggunakan pendekatan serakah sederhana.
Berdasarkan teori bahwa menggunakan array penghitungan dengan jumlah kecil bermanfaat, array penghitungan diurutkan berdasarkan jumlah penghitungannya.
Mereka kemudian diperiksa secara berurutan, dan nilai dipilih jika itu kompatibel dengan semua nilai yang digunakan sebelumnya. Jadi ini melibatkan satu lintasan linear tunggal melalui array penghitungan yang unik, di mana setiap kandidat dibandingkan dengan nilai-nilai yang sebelumnya dipilih.
Saya punya beberapa ide tentang bagaimana heuristik berpotensi ditingkatkan. Tapi ini sepertinya titik awal yang masuk akal, dan hasilnya terlihat cukup bagus.
Kode
Ini tidak sangat dioptimalkan. Saya memiliki struktur data yang lebih rumit di beberapa titik, tetapi akan membutuhkan lebih banyak pekerjaan untuk menggeneralisasikannya di luar n=8
, dan perbedaan kinerja tampaknya tidak terlalu besar.
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <sstream>
#include <iostream>
typedef uint32_t Value;
class Counter {
public:
static void setN(int n);
Counter();
Counter(Value val);
bool operator==(const Counter& rhs) const;
bool operator<(const Counter& rhs) const;
bool collides(const Counter& other) const;
private:
static const int FIELD_BITS = 4;
static const uint64_t FIELD_MASK = 0x0f;
static int m_n;
static Value m_valMask;
uint64_t fieldSum() const;
uint64_t m_fields;
};
void Counter::setN(int n) {
m_n = n;
m_valMask = (static_cast<Value>(1) << n) - 1;
}
Counter::Counter()
: m_fields(0) {
}
Counter::Counter(Value val) {
m_fields = 0;
for (int k = 0; k < m_n; ++k) {
m_fields <<= FIELD_BITS;
m_fields |= __builtin_popcount(val & m_valMask);
val >>= 1;
}
}
bool Counter::operator==(const Counter& rhs) const {
return m_fields == rhs.m_fields;
}
bool Counter::operator<(const Counter& rhs) const {
uint64_t lhsSum = fieldSum();
uint64_t rhsSum = rhs.fieldSum();
if (lhsSum < rhsSum) {
return true;
}
if (lhsSum > rhsSum) {
return false;
}
return m_fields < rhs.m_fields;
}
bool Counter::collides(const Counter& other) const {
uint64_t fields1 = m_fields;
uint64_t fields2 = other.m_fields;
for (int k = 0; k < m_n; ++k) {
uint64_t c1 = fields1 & FIELD_MASK;
uint64_t c2 = fields2 & FIELD_MASK;
if (c1 > 2 * c2 || c2 > 2 * c1) {
return false;
}
fields1 >>= FIELD_BITS;
fields2 >>= FIELD_BITS;
}
return true;
}
int Counter::m_n = 0;
Value Counter::m_valMask = 0;
uint64_t Counter::fieldSum() const {
uint64_t fields = m_fields;
uint64_t sum = 0;
for (int k = 0; k < m_n; ++k) {
sum += fields & FIELD_MASK;
fields >>= FIELD_BITS;
}
return sum;
}
typedef std::vector<Counter> Counters;
int main(int argc, char* argv[]) {
int n = 0;
std::istringstream strm(argv[1]);
strm >> n;
Counter::setN(n);
int nBit = 2 * n - 1;
Value maxVal = static_cast<Value>(1) << nBit;
Counters allCounters;
for (Value val = 0; val < maxVal; ++val) {
Counter counter(val);
allCounters.push_back(counter);
}
std::sort(allCounters.begin(), allCounters.end());
Counters::iterator uniqEnd =
std::unique(allCounters.begin(), allCounters.end());
allCounters.resize(std::distance(allCounters.begin(), uniqEnd));
Counters solCounters;
int nSol = 0;
for (Value idx = 0; idx < allCounters.size(); ++idx) {
const Counter& counter = allCounters[idx];
bool valid = true;
for (int iSol = 0; iSol < nSol; ++iSol) {
if (solCounters[iSol].collides(counter)) {
valid = false;
break;
}
}
if (valid) {
solCounters.push_back(counter);
++nSol;
}
}
std::cout << "result: " << nSol << std::endl;
return 0;
}
L1[i]/2 <= L2[i] <= 2*L1[i]
.