C ++ (heuristic): 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086
Đây là một chút đằng sau kết quả Peter Taylor, là 1-3 thấp hơn cho n=7
, 9
và 10
. Ưu điểm là nó nhanh hơn nhiều, vì vậy tôi có thể chạy nó với giá trị cao hơn n
. Và nó có thể được hiểu mà không cần bất kỳ toán học ưa thích. ;)
Mã hiện tại được kích thước để chạy đến n=15
. Thời gian chạy tăng khoảng 4 lần cho mỗi lần tăng n
. Ví dụ: thời gian là 0,008 giây n=7
, 0,07 giây n=9
, 1,34 giây n=11
, 27 giây n=13
và 9 phút n=15
.
Có hai quan sát chính tôi đã sử dụng:
- Thay vì tự vận hành các giá trị, heuristic hoạt động dựa trên việc đếm mảng. Để làm điều này, một danh sách tất cả các mảng đếm duy nhất được tạo ra đầu tiên.
- Sử dụng các mảng đếm với các giá trị nhỏ sẽ có lợi hơn, vì chúng loại bỏ ít không gian giải pháp hơn. Này được dựa trên mỗi đếm
c
trừ phạm vi c / 2
đến 2 * c
từ các giá trị khác. Đối với các giá trị nhỏ hơn c
, phạm vi này nhỏ hơn, có nghĩa là loại trừ ít giá trị hơn bằng cách sử dụng nó.
Tạo Mảng đếm độc đáo
Tôi đã dùng vũ lực cho cái này, lặp qua tất cả các giá trị, tạo ra mảng đếm cho mỗi trong số chúng và duy nhất danh sách kết quả. Điều này chắc chắn có thể được thực hiện hiệu quả hơn, nhưng nó đủ tốt cho các loại giá trị chúng tôi đang hoạt động.
Điều này là cực kỳ nhanh chóng cho các giá trị nhỏ. Đối với các giá trị lớn hơn, chi phí không đáng kể. Ví dụ, đối với n=15
, nó sử dụng khoảng 75% toàn bộ thời gian chạy. Đây chắc chắn sẽ là một khu vực để xem xét khi cố gắng đi cao hơn nhiều n=15
. Ngay cả việc sử dụng bộ nhớ để xây dựng danh sách các mảng đếm cho tất cả các giá trị cũng sẽ bắt đầu trở nên có vấn đề.
Số lượng mảng đếm duy nhất là khoảng 6% số lượng giá trị cho n=15
. Số lượng tương đối này trở nên nhỏ hơn khi n
trở nên lớn hơn.
Tham lam lựa chọn các giá trị mảng đếm
Phần chính của thuật toán chọn đếm các giá trị mảng từ danh sách được tạo bằng cách sử dụng một cách tiếp cận tham lam đơn giản.
Dựa trên lý thuyết rằng sử dụng mảng đếm với số lượng nhỏ là có lợi, mảng đếm được sắp xếp theo tổng số đếm của chúng.
Sau đó, chúng được kiểm tra theo thứ tự và một giá trị được chọn nếu nó tương thích với tất cả các giá trị được sử dụng trước đó. Vì vậy, điều này liên quan đến một lần truyền tuyến tính duy nhất thông qua các mảng đếm duy nhất, trong đó mỗi ứng cử viên được so sánh với các giá trị đã được chọn trước đó.
Tôi có một số ý tưởng về cách heuristic có thể được cải thiện. Nhưng điều này có vẻ như là một điểm khởi đầu hợp lý, và kết quả có vẻ khá tốt.
Mã
Điều này không được tối ưu hóa cao. Tôi đã có một cấu trúc dữ liệu phức tạp hơn tại một số điểm, nhưng nó sẽ cần nhiều công việc hơn để khái quát hóa nó hơn nữa n=8
, và sự khác biệt về hiệu suất dường như không đáng kể.
#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]
.