C ++ (हेयुरिस्टिक): 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086
यह पीटर टेलर के परिणाम से थोड़ा पीछे है n=7
, 9
और 1 से 3 कम है , और 10
। लाभ यह है कि यह बहुत तेज़ है, इसलिए मैं इसे उच्च मूल्यों के लिए चला सकता हूं n
। और इसे बिना किसी फैंसी गणित के समझा जा सकता है। ;)
वर्तमान कोड को चलाने के लिए आयाम दिया गया है n=15
। प्रत्येक में वृद्धि के लिए 4 के कारक द्वारा रन समय में लगभग वृद्धि होती है n
। उदाहरण के लिए, यह 0.008 सेकंड के लिए n=7
, 0.07 सेकंड के लिए n=9
, 1.34 सेकंड के लिए n=11
, 27 सेकंड के लिए n=13
, और 9 मिनट के लिए है n=15
।
मेरे द्वारा उपयोग की जाने वाली दो प्रमुख टिप्पणियां हैं:
- मूल्यों पर खुद को संचालित करने के बजाय, अनुमानी मतगणना सरणियों पर काम करता है। ऐसा करने के लिए, सभी अद्वितीय गिनती सरणियों की एक सूची पहले उत्पन्न होती है।
- छोटे मानों के साथ गिनती सरणियों का उपयोग करना अधिक फायदेमंद है, क्योंकि वे समाधान स्थान के कम को खत्म करते हैं। यह प्रत्येक संख्या के आधार पर किया जाता है
c
की सीमा को छोड़कर c / 2
के लिए 2 * c
अन्य मूल्यों से। के छोटे मानों के लिए c
, यह श्रेणी छोटी है, जिसका अर्थ है कि इसका उपयोग करके कम मूल्यों को बाहर रखा गया है।
अद्वितीय काउंटिंग एरे उत्पन्न करें
मैं इस पर एक क्रूर बल गया, सभी मूल्यों के माध्यम से पुनरावृत्ति, उनमें से प्रत्येक के लिए गिनती सरणी का निर्माण, और परिणामी सूची को अद्वितीय बनाना। यह निश्चित रूप से अधिक कुशलता से किया जा सकता है, लेकिन यह उन मूल्यों के प्रकार के लिए पर्याप्त है जो हम संचालित कर रहे हैं।
यह छोटे मूल्यों के लिए अत्यंत त्वरित है। बड़े मूल्यों के लिए, ओवरहेड पर्याप्त हो जाता है। उदाहरण के लिए, n=15
पूरे रनटाइम का लगभग 75% उपयोग करता है। यह निश्चित रूप से देखने का एक क्षेत्र होगा, जब इससे कहीं अधिक जाने की कोशिश की जा रही है n=15
। यहां तक कि सभी मूल्यों के लिए गिनती सरणियों की सूची बनाने के लिए भी स्मृति उपयोग समस्याग्रस्त होना शुरू हो जाएगा।
अद्वितीय गिनती सरणियों की संख्या के लिए मूल्यों की संख्या का लगभग 6% है n=15
। यह सापेक्ष संख्या जितनी n
बड़ी होती जाती है उतनी छोटी होती जाती है।
काउंटिंग एरे वैल्यूज़ का लालची चयन
एल्गोरिथ्म का मुख्य भाग एक साधारण लालची दृष्टिकोण का उपयोग करके उत्पन्न सूची से गिनती मान का चयन करता है।
इस सिद्धांत के आधार पर कि छोटे काउंट के साथ गिनती सरणियों का उपयोग करना फायदेमंद है, गिनती के सरणियों को उनके मायने के योग से हल किया जाता है।
फिर उन्हें क्रम में जांचा जाता है, और यदि पहले से उपयोग किए गए सभी मूल्यों के साथ संगत है, तो एक मूल्य चुना जाता है। इसलिए इसमें अद्वितीय गिनती सरणियों के माध्यम से एक एकल रैखिक पास शामिल है, जहां प्रत्येक उम्मीदवार की तुलना उन मूल्यों से की जाती है जो पहले चुने गए थे।
मेरे पास कुछ विचार हैं कि कैसे हेयुरिस्टिक को बेहतर बनाया जा सकता है। लेकिन यह एक उचित शुरुआती बिंदु की तरह लग रहा था, और परिणाम काफी अच्छे लग रहे थे।
कोड
यह अत्यधिक अनुकूलित नहीं है। मेरे पास कुछ बिंदु पर अधिक विस्तृत डेटा संरचना थी, लेकिन इसे परे सामान्य करने के लिए इसे और अधिक कार्य की आवश्यकता होगी n=8
, और प्रदर्शन में अंतर बहुत महत्वपूर्ण नहीं था।
#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]
।