C ++ 11 - เกือบจะทำงานแล้ว :)
หลังจากอ่านบทความนี้ฉันได้รวบรวมสติปัญญาจากคนที่ทำงานเป็นเวลา 25 ปีกับปัญหาที่ซับซ้อนน้อยกว่าในการนับเส้นทางที่หลีกเลี่ยงตัวเองบนตาข่ายสี่เหลี่ยม
#include <cassert>
#include <ctime>
#include <sstream>
#include <vector>
#include <algorithm> // sort
using namespace std;
// theroretical max snake lenght (the code would need a few decades to process that value)
#define MAX_LENGTH ((int)(1+8*sizeof(unsigned)))
#ifndef _MSC_VER
#ifndef QT_DEBUG // using Qt IDE for g++ builds
#define NDEBUG
#endif
#endif
#ifdef NDEBUG
inline void tprintf(const char *, ...){}
#else
#define tprintf printf
#endif
void panic(const char * msg)
{
printf("PANIC: %s\n", msg);
exit(-1);
}
// ============================================================================
// fast bit reversal
// ============================================================================
unsigned bit_reverse(register unsigned x, unsigned len)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return((x >> 16) | (x << 16)) >> (32-len);
}
// ============================================================================
// 2D geometry (restricted to integer coordinates and right angle rotations)
// ============================================================================
// points using integer- or float-valued coordinates
template<typename T>struct tTypedPoint;
typedef int tCoord;
typedef double tFloatCoord;
typedef tTypedPoint<tCoord> tPoint;
typedef tTypedPoint<tFloatCoord> tFloatPoint;
template <typename T>
struct tTypedPoint {
T x, y;
template<typename U> tTypedPoint(const tTypedPoint<U>& from) : x((T)from.x), y((T)from.y) {} // conversion constructor
tTypedPoint() {}
tTypedPoint(T x, T y) : x(x), y(y) {}
tTypedPoint(const tTypedPoint& p) { *this = p; }
tTypedPoint operator+ (const tTypedPoint & p) const { return{ x + p.x, y + p.y }; }
tTypedPoint operator- (const tTypedPoint & p) const { return{ x - p.x, y - p.y }; }
tTypedPoint operator* (T scalar) const { return{ x * scalar, y * scalar }; }
tTypedPoint operator/ (T scalar) const { return{ x / scalar, y / scalar }; }
bool operator== (const tTypedPoint & p) const { return x == p.x && y == p.y; }
bool operator!= (const tTypedPoint & p) const { return !operator==(p); }
T dot(const tTypedPoint &p) const { return x*p.x + y * p.y; } // dot product
int cross(const tTypedPoint &p) const { return x*p.y - y * p.x; } // z component of cross product
T norm2(void) const { return dot(*this); }
// works only with direction = 1 (90° right) or -1 (90° left)
tTypedPoint rotate(int direction) const { return{ direction * y, -direction * x }; }
tTypedPoint rotate(int direction, const tTypedPoint & center) const { return (*this - center).rotate(direction) + center; }
// used to compute length of a ragdoll snake segment
unsigned manhattan_distance(const tPoint & p) const { return abs(x-p.x) + abs(y-p.y); }
};
struct tArc {
tPoint c; // circle center
tFloatPoint middle_vector; // vector splitting the arc in half
tCoord middle_vector_norm2; // precomputed for speed
tFloatCoord dp_limit;
tArc() {}
tArc(tPoint c, tPoint p, int direction) : c(c)
{
tPoint r = p - c;
tPoint end = r.rotate(direction);
middle_vector = ((tFloatPoint)(r+end)) / sqrt(2); // works only for +-90° rotations. The vector should be normalized to circle radius in the general case
middle_vector_norm2 = r.norm2();
dp_limit = ((tFloatPoint)r).dot(middle_vector);
assert (middle_vector == tPoint(0, 0) || dp_limit != 0);
}
bool contains(tFloatPoint p) // p must be a point on the circle
{
if ((p-c).dot(middle_vector) >= dp_limit)
{
return true;
}
else return false;
}
};
// returns the point of line (p1 p2) that is closest to c
// handles degenerate case p1 = p2
tPoint line_closest_point(tPoint p1, tPoint p2, tPoint c)
{
if (p1 == p2) return{ p1.x, p1.y };
tPoint p1p2 = p2 - p1;
tPoint p1c = c - p1;
tPoint disp = (p1p2 * p1c.dot(p1p2)) / p1p2.norm2();
return p1 + disp;
}
// variant of closest point computation that checks if the projection falls within the segment
bool closest_point_within(tPoint p1, tPoint p2, tPoint c, tPoint & res)
{
tPoint p1p2 = p2 - p1;
tPoint p1c = c - p1;
tCoord nk = p1c.dot(p1p2);
if (nk <= 0) return false;
tCoord n = p1p2.norm2();
if (nk >= n) return false;
res = p1 + p1p2 * (nk / n);
return true;
}
// tests intersection of line (p1 p2) with an arc
bool inter_seg_arc(tPoint p1, tPoint p2, tArc arc)
{
tPoint m = line_closest_point(p1, p2, arc.c);
tCoord r2 = arc.middle_vector_norm2;
tPoint cm = m - arc.c;
tCoord h2 = cm.norm2();
if (r2 < h2) return false; // no circle intersection
tPoint p1p2 = p2 - p1;
tCoord n2p1p2 = p1p2.norm2();
// works because by construction p is on (p1 p2)
auto in_segment = [&](const tFloatPoint & p) -> bool
{
tFloatCoord nk = p1p2.dot(p - p1);
return nk >= 0 && nk <= n2p1p2;
};
if (r2 == h2) return arc.contains(m) && in_segment(m); // tangent intersection
//if (p1 == p2) return false; // degenerate segment located inside circle
assert(p1 != p2);
tFloatPoint u = (tFloatPoint)p1p2 * sqrt((r2-h2)/n2p1p2); // displacement on (p1 p2) from m to one intersection point
tFloatPoint i1 = m + u;
if (arc.contains(i1) && in_segment(i1)) return true;
tFloatPoint i2 = m - u;
return arc.contains(i2) && in_segment(i2);
}
// ============================================================================
// compact storage of a configuration (64 bits)
// ============================================================================
struct sConfiguration {
unsigned partition;
unsigned folding;
explicit sConfiguration() {}
sConfiguration(unsigned partition, unsigned folding) : partition(partition), folding(folding) {}
// add a bend
sConfiguration bend(unsigned joint, int rotation) const
{
sConfiguration res;
unsigned joint_mask = 1 << joint;
res.partition = partition | joint_mask;
res.folding = folding;
if (rotation == -1) res.folding |= joint_mask;
return res;
}
// textual representation
string text(unsigned length) const
{
ostringstream res;
unsigned f = folding;
unsigned p = partition;
int segment_len = 1;
int direction = 1;
for (size_t i = 1; i != length; i++)
{
if (p & 1)
{
res << segment_len * direction << ',';
direction = (f & 1) ? -1 : 1;
segment_len = 1;
}
else segment_len++;
p >>= 1;
f >>= 1;
}
res << segment_len * direction;
return res.str();
}
// for final sorting
bool operator< (const sConfiguration& c) const
{
return (partition == c.partition) ? folding < c.folding : partition < c.partition;
}
};
// ============================================================================
// static snake geometry checking grid
// ============================================================================
typedef unsigned tConfId;
class tGrid {
vector<tConfId>point;
tConfId current;
size_t snake_len;
int min_x, max_x, min_y, max_y;
size_t x_size, y_size;
size_t raw_index(const tPoint& p) { bound_check(p); return (p.x - min_x) + (p.y - min_y) * x_size; }
void bound_check(const tPoint& p) const { assert(p.x >= min_x && p.x <= max_x && p.y >= min_y && p.y <= max_y); }
void set(const tPoint& p)
{
point[raw_index(p)] = current;
}
bool check(const tPoint& p)
{
if (point[raw_index(p)] == current) return false;
set(p);
return true;
}
public:
tGrid(int len) : current(-1), snake_len(len)
{
min_x = -max(len - 3, 0);
max_x = max(len - 0, 0);
min_y = -max(len - 1, 0);
max_y = max(len - 4, 0);
x_size = max_x - min_x + 1;
y_size = max_y - min_y + 1;
point.assign(x_size * y_size, current);
}
bool check(sConfiguration c)
{
current++;
tPoint d(1, 0);
tPoint p(0, 0);
set(p);
for (size_t i = 1; i != snake_len; i++)
{
p = p + d;
if (!check(p)) return false;
if (c.partition & 1) d = d.rotate((c.folding & 1) ? -1 : 1);
c.folding >>= 1;
c.partition >>= 1;
}
return check(p + d);
}
};
// ============================================================================
// snake ragdoll
// ============================================================================
class tSnakeDoll {
vector<tPoint>point; // snake geometry. Head at (0,0) pointing right
// allows to check for collision with the area swept by a rotating segment
struct rotatedSegment {
struct segment { tPoint a, b; };
tPoint org;
segment end;
tArc arc[3];
bool extra_arc; // see if third arc is needed
// empty constructor to avoid wasting time in vector initializations
rotatedSegment(){}
// copy constructor is mandatory for vectors *but* shall never be used, since we carefully pre-allocate vector memory
rotatedSegment(const rotatedSegment &){ assert(!"rotatedSegment should never have been copy-constructed"); }
// rotate a segment
rotatedSegment(tPoint pivot, int rotation, tPoint o1, tPoint o2)
{
arc[0] = tArc(pivot, o1, rotation);
arc[1] = tArc(pivot, o2, rotation);
tPoint middle;
extra_arc = closest_point_within(o1, o2, pivot, middle);
if (extra_arc) arc[2] = tArc(pivot, middle, rotation);
org = o1;
end = { o1.rotate(rotation, pivot), o2.rotate(rotation, pivot) };
}
// check if a segment intersects the area swept during rotation
bool intersects(tPoint p1, tPoint p2) const
{
auto print_arc = [&](int a) { tprintf("(%d,%d)(%d,%d) -> %d (%d,%d)[%f,%f]", p1.x, p1.y, p2.x, p2.y, a, arc[a].c.x, arc[a].c.y, arc[a].middle_vector.x, arc[a].middle_vector.y); };
if (p1 == org) return false; // pivot is the only point allowed to intersect
if (inter_seg_arc(p1, p2, arc[0]))
{
print_arc(0);
return true;
}
if (inter_seg_arc(p1, p2, arc[1]))
{
print_arc(1);
return true;
}
if (extra_arc && inter_seg_arc(p1, p2, arc[2]))
{
print_arc(2);
return true;
}
return false;
}
};
public:
sConfiguration configuration;
bool valid;
// holds results of a folding attempt
class snakeFolding {
friend class tSnakeDoll;
vector<rotatedSegment>segment; // rotated segments
unsigned joint;
int direction;
size_t i_rotate;
// pre-allocate rotated segments
void reserve(size_t length)
{
segment.clear(); // this supposedly does not release vector storage memory
segment.reserve(length);
}
// handle one segment rotation
void rotate(tPoint pivot, int rotation, tPoint o1, tPoint o2)
{
segment.emplace_back(pivot, rotation, o1, o2);
}
public:
// nothing done during construction
snakeFolding(unsigned size)
{
segment.reserve (size);
}
};
// empty default constructor to avoid wasting time in array/vector inits
tSnakeDoll() {}
// constructs ragdoll from compressed configuration
tSnakeDoll(unsigned size, unsigned generator, unsigned folding) : point(size), configuration(generator,folding)
{
tPoint direction(1, 0);
tPoint current = { 0, 0 };
size_t p = 0;
point[p++] = current;
for (size_t i = 1; i != size; i++)
{
current = current + direction;
if (generator & 1)
{
direction.rotate((folding & 1) ? -1 : 1);
point[p++] = current;
}
folding >>= 1;
generator >>= 1;
}
point[p++] = current;
point.resize(p);
}
// constructs the initial flat snake
tSnakeDoll(int size) : point(2), configuration(0,0), valid(true)
{
point[0] = { 0, 0 };
point[1] = { size, 0 };
}
// constructs a new folding with one added rotation
tSnakeDoll(const tSnakeDoll & parent, unsigned joint, int rotation, tGrid& grid)
{
// update configuration
configuration = parent.configuration.bend(joint, rotation);
// locate folding point
unsigned p_joint = joint+1;
tPoint pivot;
size_t i_rotate = 0;
for (size_t i = 1; i != parent.point.size(); i++)
{
unsigned len = parent.point[i].manhattan_distance(parent.point[i - 1]);
if (len > p_joint)
{
pivot = parent.point[i - 1] + ((parent.point[i] - parent.point[i - 1]) / len) * p_joint;
i_rotate = i;
break;
}
else p_joint -= len;
}
// rotate around joint
snakeFolding fold (parent.point.size() - i_rotate);
fold.rotate(pivot, rotation, pivot, parent.point[i_rotate]);
for (size_t i = i_rotate + 1; i != parent.point.size(); i++) fold.rotate(pivot, rotation, parent.point[i - 1], parent.point[i]);
// copy unmoved points
point.resize(parent.point.size()+1);
size_t i;
for (i = 0; i != i_rotate; i++) point[i] = parent.point[i];
// copy rotated points
for (; i != parent.point.size(); i++) point[i] = fold.segment[i - i_rotate].end.a;
point[i] = fold.segment[i - 1 - i_rotate].end.b;
// static configuration check
valid = grid.check (configuration);
// check collisions with swept arcs
if (valid && parent.valid) // ;!; parent.valid test is temporary
{
for (const rotatedSegment & s : fold.segment)
for (size_t i = 0; i != i_rotate; i++)
{
if (s.intersects(point[i+1], point[i]))
{
//printf("! %s => %s\n", parent.trace().c_str(), trace().c_str());//;!;
valid = false;
break;
}
}
}
}
// trace
string trace(void) const
{
size_t len = 0;
for (size_t i = 1; i != point.size(); i++) len += point[i - 1].manhattan_distance(point[i]);
return configuration.text(len);
}
};
// ============================================================================
// snake twisting engine
// ============================================================================
class cSnakeFolder {
int length;
unsigned num_joints;
tGrid grid;
// filter redundant configurations
bool is_unique (sConfiguration c)
{
unsigned reverse_p = bit_reverse(c.partition, num_joints);
if (reverse_p < c.partition)
{
tprintf("P cut %s\n", c.text(length).c_str());
return false;
}
else if (reverse_p == c.partition) // filter redundant foldings
{
unsigned first_joint_mask = c.partition & (-c.partition); // insulates leftmost bit
unsigned reverse_f = bit_reverse(c.folding, num_joints);
if (reverse_f & first_joint_mask) reverse_f = ~reverse_f & c.partition;
if (reverse_f > c.folding)
{
tprintf("F cut %s\n", c.text(length).c_str());
return false;
}
}
return true;
}
// recursive folding
void fold(tSnakeDoll snake, unsigned first_joint)
{
// count unique configurations
if (snake.valid && is_unique(snake.configuration)) num_configurations++;
// try to bend remaining joints
for (size_t joint = first_joint; joint != num_joints; joint++)
{
// right bend
tprintf("%s -> %s\n", snake.configuration.text(length).c_str(), snake.configuration.bend(joint,1).text(length).c_str());
fold(tSnakeDoll(snake, joint, 1, grid), joint + 1);
// left bend, except for the first joint
if (snake.configuration.partition != 0)
{
tprintf("%s -> %s\n", snake.configuration.text(length).c_str(), snake.configuration.bend(joint, -1).text(length).c_str());
fold(tSnakeDoll(snake, joint, -1, grid), joint + 1);
}
}
}
public:
// count of found configurations
unsigned num_configurations;
// constructor does all the work :)
cSnakeFolder(int n) : length(n), grid(n), num_configurations(0)
{
num_joints = length - 1;
// launch recursive folding
fold(tSnakeDoll(length), 0);
}
};
// ============================================================================
// here we go
// ============================================================================
int main(int argc, char * argv[])
{
#ifdef NDEBUG
if (argc != 2) panic("give me a snake length or else");
int length = atoi(argv[1]);
#else
(void)argc; (void)argv;
int length = 12;
#endif // NDEBUG
if (length <= 0 || length >= MAX_LENGTH) panic("a snake of that length is hardly foldable");
time_t start = time(NULL);
cSnakeFolder snakes(length);
time_t duration = time(NULL) - start;
printf ("Found %d configuration%c of length %d in %lds\n", snakes.num_configurations, (snakes.num_configurations == 1) ? '\0' : 's', length, duration);
return 0;
}
สร้างปฏิบัติการ
รวบรวมกับ
ฉันใช้ MinGW ภายใต้ Win7 กับ g ++ 4.8 สำหรับการสร้าง "linux" ดังนั้นการพกพาจึงไม่รับประกัน 100%g++ -O3 -std=c++11
นอกจากนี้ยังใช้งานได้ (เรียงลำดับ) กับโครงการ MSVC2013 มาตรฐาน
โดย Undefining NDEBUG
คุณจะได้รับร่องรอยของการดำเนินการขั้นตอนวิธีการและการสรุปของการกำหนดค่าพบ
การแสดง
มีหรือไม่มีตารางแฮช, ไมโครซอฟท์ดำเนินการคอมไพเลอร์อย่างน่าสังเวช: กรัม ++ สร้างเป็น3 ครั้งได้เร็วขึ้น
อัลกอริทึมไม่ได้ใช้หน่วยความจำเลย
เนื่องจากการตรวจสอบการชนกันของข้อมูลนั้นอยู่ใน O (n) เวลาในการคำนวณควรอยู่ใน O (nk n ) โดยที่ k ต่ำกว่า 3 เล็กน้อย
ใน i3-2100@3.1GHz ของฉัน n = 17 ใช้เวลาประมาณ 1:30 (ประมาณ 2 ล้าน) งู / นาที)
ฉันไม่ได้ทำการปรับให้เหมาะสม แต่ฉันจะไม่คาดหวังว่าจะได้รับมากกว่า x3 ดังนั้นโดยทั่วไปฉันสามารถหวังว่าจะถึง n = 20 ภายใต้หนึ่งชั่วโมงหรือ n = 24 ภายใต้หนึ่งวัน
การเข้าถึงรูปร่างที่ไม่สามารถทนทานได้ที่รู้จักกันเป็นครั้งแรก (n = 31) จะใช้เวลาสองถึงสามปีถึงหนึ่งทศวรรษโดยไม่มีไฟฟ้าดับ
การนับรูปร่าง
NขนาดงูมีN-1ข้อต่อ
ข้อต่อแต่ละข้อสามารถซ้ายหรือโค้งงอไปทางซ้ายหรือขวา (3 ความเป็นไปได้)
จำนวนพับที่เป็นไปได้ดังนั้นจึงเป็น 3 N-1
การชนจะลดจำนวนลงบ้างดังนั้นจำนวนจริงจึงใกล้เคียงกับ 2.7 N-1
อย่างไรก็ตามการพับหลายครั้งทำให้มีรูปร่างเหมือนกัน
รูปร่างสองรูปเหมือนกันหากมีการหมุนหรือมีสัญลักษณ์ที่สามารถแปลงเป็นรูปร่างอื่นได้
ลองกำหนดกลุ่มเป็นส่วนตรง ๆ ของส่วนที่พับ
ตัวอย่างเช่นงูขนาด 5 ที่พับที่ข้อต่อ 2 จะมี 2 ส่วน (ยาว 1 หน่วยและยาว 3 วินาที)
ส่วนแรกจะถูกตั้งชื่อตามหัวและท้ายสุด
โดยการประชุมเราวางหัวงูในแนวนอนโดยหันลำตัวไปทางขวา (เช่นในรูปแรกของ OP)
เรากำหนดรูปที่กำหนดด้วยรายการความยาวของส่วนที่เซ็นชื่อพร้อมความยาวเป็นบวกที่ระบุการพับด้านขวาและลบในทางลบ
ความยาวเริ่มต้นเป็นค่าบวกโดยการประชุม
แยกส่วนและโค้ง
หากเราพิจารณาเพียงวิธีที่แตกต่างกันงูที่มีความยาว N สามารถแบ่งออกเป็นส่วน ๆ ได้เราจะสิ้นสุดด้วยการแบ่งส่วนเหมือนกับองค์ประกอบของ N
การใช้อัลกอริธึมแบบเดียวกับที่แสดงในหน้า wiki ทำให้ง่ายต่อการสร้างพาร์ติชั่นที่เป็นไปได้ทั้งหมดของงู2 N-1
แต่ละพาร์ติชั่นจะสร้างรอยพับที่เป็นไปได้ทั้งหมดโดยการโค้งซ้ายหรือขวากับข้อต่อทั้งหมด หนึ่งพับดังกล่าวจะถูกเรียกว่าการกำหนดค่า
พาร์ติชันที่เป็นไปได้ทั้งหมดสามารถถูกแทนด้วยจำนวนเต็มของบิต N-1 โดยที่แต่ละบิตแทนการมีอยู่ของรอยต่อ เราจะเรียกสิ่งนี้ว่าจำนวนเต็มกำเนิด
พาร์ติชันการตัดแต่งกิ่ง
โดยการสังเกตว่าการโค้งงอพาร์ติชันที่กำหนดจากหัวลงนั้นเทียบเท่ากับการดัดฉากกั้นจากหางขึ้นเราสามารถหาพาร์ติชัน symetrical คู่และกำจัดหนึ่งในสอง
ตัวกำเนิดของพาร์ติชัน symetrical เป็นตัวกำเนิดของพาร์ติชันที่เขียนในลำดับบิตย้อนกลับซึ่งง่ายต่อการตรวจสอบและราคาถูก
สิ่งนี้จะกำจัดเกือบครึ่งหนึ่งของพาร์ติชั่นที่เป็นไปได้ข้อยกเว้นการเป็นพาร์ติชั่นด้วยเครื่องกำเนิดไฟฟ้า "palindromic" ที่ไม่เปลี่ยนแปลงโดยการสลับบิต (เช่น 00100100)
การดูแล symetries แนวนอน
ด้วยการประชุมของเรา (งูเริ่มชี้ไปทางขวา) การโค้งงอครั้งแรกที่ใช้ทางด้านขวาจะทำให้เกิดรอยพับในแนวนอนซึ่งจะเป็นแนวนอนแนวตั้งจากแนวโค้งที่แตกต่างจากโค้งแรกเท่านั้น
หากเราตัดสินใจว่าการโค้งงอครั้งแรกจะอยู่ทางด้านขวาเราจะกำจัด symetrics แนวนอนทั้งหมดในการปัดครั้งใหญ่
การถู palindromes
การตัดสองอย่างนี้มีประสิทธิภาพ แต่ไม่เพียงพอที่จะดูแล palindromes ที่น่ารำคาญเหล่านี้
การตรวจสอบอย่างละเอียดที่สุดในกรณีทั่วไปมีดังนี้:
พิจารณาการกำหนดค่า C ด้วยพาร์ติชัน palindromic
- ถ้าเรากลับโค้งทุกอันใน C เราจะได้สมมาตรแนวนอนของ C
- ถ้าเราย้อนกลับ C (ใช้โค้งจากหางขึ้นไป) เราจะได้ตัวเลขที่หมุนเหมือนเดิม
- ถ้าเราทั้งคู่กลับด้านและกลับด้าน C เราจะได้ตัวเลขที่หมุนไปทางซ้ายเหมือนกัน
เราสามารถตรวจสอบการกำหนดค่าใหม่กับ 3 รายการอื่น ๆ อย่างไรก็ตามเนื่องจากเราสร้างการกำหนดค่าเริ่มต้นด้วยการเลี้ยวขวาเท่านั้นเราจึงมีการตรวจสอบความเป็นไปได้เพียงอย่างเดียว:
- ฤCษี C จะเริ่มต้นด้วยการเลี้ยวซ้ายซึ่งเป็นไปไม่ได้ที่จะสร้างซ้ำ
- ออกจากการกำหนดค่าย้อนกลับและกลับตรงกันข้ามเพียงหนึ่งจะเริ่มต้นด้วยการเลี้ยวขวา
นั่นคือการกำหนดค่าเดียวเท่านั้นที่เราสามารถทำซ้ำได้
กำจัดสิ่งที่ซ้ำกันโดยไม่มีที่เก็บข้อมูลใด ๆ
วิธีการเริ่มต้นของฉันคือการจัดเก็บการกำหนดค่าทั้งหมดในตารางแฮชขนาดใหญ่เพื่อกำจัดรายการที่ซ้ำกันโดยการตรวจสอบสถานะของการกำหนดค่า symetric ที่คำนวณก่อนหน้านี้
ต้องขอบคุณบทความดังกล่าวข้างต้นทำให้เห็นได้ชัดว่าเนื่องจากพาร์ติชั่นและการพับจะถูกเก็บไว้เป็นบิตฟิลด์พวกเขาสามารถเปรียบเทียบได้เหมือนกับค่าตัวเลขใด ๆ
ดังนั้นในการกำจัดสมาชิกของคู่สมมาตรคุณสามารถเปรียบเทียบทั้งสององค์ประกอบและรักษาสมาชิกที่เล็กที่สุดอย่างเป็นระบบ (หรือสมาชิกที่ยิ่งใหญ่ที่สุดเท่าที่คุณต้องการ)
ดังนั้นการทดสอบการกำหนดค่าสำหรับจำนวนที่ซ้ำกันเพื่อคำนวณพาร์ติชัน symetric และหากทั้งสองเหมือนกันการพับ ไม่จำเป็นต้องใช้หน่วยความจำเลย
ลำดับของรุ่น
การตรวจสอบการชนอย่างชัดเจนจะเป็นส่วนที่ใช้เวลามากที่สุดดังนั้นการลดการคำนวณเหล่านี้จึงเป็นการประหยัดเวลาที่สำคัญ
ทางออกที่เป็นไปได้คือมี "ragdoll snake" ที่จะเริ่มในการกำหนดค่าแบบแบนและค่อย ๆ งอเพื่อหลีกเลี่ยงการคำนวณรูปทรงเรขาคณิตของงูทั้งหมดสำหรับการกำหนดค่าที่เป็นไปได้แต่ละครั้ง
โดยการเลือกลำดับการทดสอบการกำหนดค่าเพื่อให้ ragdoll ส่วนใหญ่ถูกเก็บไว้สำหรับจำนวนข้อต่อแต่ละข้อเราสามารถ จำกัด จำนวนอินสแตนซ์เป็น N-1
ฉันใช้การสแกนซ้ำของสาเกจากหางลงโดยเพิ่มรอยต่อเดี่ยวในแต่ละระดับ ดังนั้นอินสแตนซ์ ragdoll ใหม่จะถูกสร้างขึ้นที่ด้านบนของการกำหนดค่าหลักด้วยโค้งงอ aditional เดียว
ซึ่งหมายความว่าการโค้งถูกนำไปใช้ตามลำดับซึ่งดูเหมือนจะเพียงพอที่จะหลีกเลี่ยงการชนกันของตัวเองในเกือบทุกกรณี
เมื่อตรวจพบการชนกันของตัวเองการโค้งที่นำไปสู่การกระทำผิดจะถูกนำไปใช้ในคำสั่งซื้อที่เป็นไปได้ทั้งหมดจนกว่าจะพบการพับที่ถูกต้องหรือการรวมทั้งหมดหมดลง
ตรวจสอบแบบคงที่
ก่อนที่จะคิดเกี่ยวกับชิ้นส่วนที่เคลื่อนไหวฉันพบว่ามันมีประสิทธิภาพมากขึ้นในการทดสอบรูปทรงสุดท้ายของงูสำหรับจุดตัดด้วยตนเอง
ทำได้โดยการวาดงูบนกริด จุดที่เป็นไปได้แต่ละจุดจะถูกพล็อตจากหัวลง หากมีจุดตัดด้วยตนเองอย่างน้อยคู่ของคะแนนจะตกอยู่ในตำแหน่งเดียวกัน สิ่งนี้ต้องการพล็อต N ที่แน่นอนสำหรับการกำหนดค่างูใด ๆ เป็นเวลา O (N) คงที่
ข้อได้เปรียบหลักของวิธีนี้คือการทดสอบแบบสแตติกเพียงอย่างเดียวจะเลือกเส้นทางการหลีกเลี่ยงตัวเองที่ถูกต้องบนตารางสี่เหลี่ยมซึ่งช่วยให้ทดสอบอัลกอริทึมทั้งหมดโดยยับยั้งการตรวจจับการชนแบบไดนามิกและทำให้แน่ใจว่าเราพบจำนวนเส้นทางที่ถูกต้อง
ตรวจสอบแบบไดนามิก
เมื่องูพับหนึ่งรอบรอยต่อแต่ละส่วนที่หมุนจะกวาดพื้นที่ที่มีรูปร่างเป็นอะไรนอกจากเล็กน้อย
เห็นได้ชัดว่าคุณสามารถตรวจสอบการชนกันโดยการทดสอบการรวมอยู่ในพื้นที่กวาดทั้งหมดเป็นรายบุคคล การตรวจสอบทั่วโลกจะมีประสิทธิภาพมากขึ้น แต่ให้ความซับซ้อนด้านที่ฉันไม่สามารถคิดได้ (ยกเว้นอาจใช้ GPU เพื่อวาดทุกพื้นที่และดำเนินการตรวจสอบระดับโลก)
เนื่องจากการทดสอบแบบสถิตจะดูแลตำแหน่งเริ่มต้นและจุดสิ้นสุดของแต่ละเซกเมนต์เราเพียงแค่ต้องตรวจสอบจุดตัดด้วยส่วนโค้งที่กวาดโดยแต่ละส่วนที่หมุน
หลังจากการสนทนาที่น่าสนใจกับ trichoplax และJavaScript เล็กน้อยเพื่อให้ได้แบริ่งของฉันฉันมาด้วยวิธีนี้:
หากต้องการลองใส่เป็นคำสองสามคำหากคุณโทรหา
- Cศูนย์กลางของการหมุน
- Sส่วนที่หมุนได้ของความยาวและทิศทางที่ไม่มีC ,
- LสายยืดS
- Hเส้นตั้งฉากกับLผ่านC ,
- ผมตัดของLและH ,
(ที่มา: free.fr )
สำหรับเซ็กเมนต์ใด ๆ ที่ไม่มีIพื้นที่กวาดถูกผูกไว้ด้วย 2 ส่วนโค้ง (และ 2 เซ็กเมนต์ได้รับการตรวจสอบแบบคงที่แล้ว)
ถ้าฉันตกอยู่ในกลุ่มอาร์คจะถูกกวาดโดยฉันจะต้องนำมาพิจารณาด้วย
ซึ่งหมายความว่าเราสามารถตรวจสอบแต่ละส่วนที่ไม่ได้ทำการเคลื่อนย้ายเทียบกับแต่ละเซกเมนต์หมุนด้วย 2 หรือ 3 เซกเมนต์พร้อมกับส่วนโค้ง
ฉันใช้เรขาคณิตแบบเวกเตอร์เพื่อหลีกเลี่ยงฟังก์ชันตรีโกณมิติโดยสิ้นเชิง
การดำเนินการของ Vector จะสร้างรหัสที่กะทัดรัดและอ่านได้ค่อนข้างง่าย
การแยกเซกเมนต์กับอาร์คต้องใช้เวกเตอร์จุดลอยตัว แต่ตรรกะควรมีภูมิคุ้มกันต่อข้อผิดพลาดในการปัดเศษ
ฉันพบโซลูชันที่หรูหราและมีประสิทธิภาพนี้ในโพสต์ฟอรัมที่ไม่ชัดเจน ฉันสงสัยว่าทำไมมันไม่เผยแพร่อย่างกว้างขวางมากขึ้น
มันใช้งานได้หรือไม่
การยับยั้งการตรวจจับการชนกันแบบไดนามิกทำให้เส้นทางการหลีกเลี่ยงตัวเองที่ถูกต้องนับได้ถึง n = 19 ดังนั้นฉันค่อนข้างมั่นใจว่าโครงร่างทั่วโลกทำงานได้
การตรวจจับการชนกันแบบไดนามิกสร้างผลลัพธ์ที่สอดคล้องกันแม้ว่าการตรวจสอบการโค้งตามลำดับจะหายไป (ตอนนี้)
เป็นผลให้โปรแกรมนับงูที่สามารถโค้งงอจากหัวลง (เช่นกับข้อต่อพับตามลำดับเพื่อเพิ่มระยะห่างจากหัว)