C ++ 11 - gần như hoạt động :)
Sau khi đọc bài viết này , tôi đã thu thập được một chút trí tuệ từ anh chàng đó, người dường như đã làm việc trong 25 năm về vấn đề ít phức tạp hơn là đếm các con đường tự tránh trên một mạng vuông.
#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;
}
Xây dựng thực thi
Biên dịch với
tôi sử dụng MinGW trong Win7 với g ++ 4.8 cho các bản dựng "linux", vì vậy tính di động không được đảm bảo 100%.g++ -O3 -std=c++11
Nó cũng hoạt động (loại) với một dự án MSVC2013 tiêu chuẩn
Bằng cách xác định NDEBUG
, bạn có được dấu vết thực hiện thuật toán và tóm tắt các cấu hình được tìm thấy.
Biểu diễn
có hoặc không có bảng băm, trình biên dịch Microsoft thực hiện một cách thảm hại: g ++ build nhanh hơn 3 lần .
Thuật toán sử dụng thực tế không có bộ nhớ.
Vì kiểm tra va chạm gần bằng O (n), thời gian tính toán phải ở O (nk n ), với k hơi thấp hơn 3.
Trên i3-2100@3.1GHz của tôi, n = 17 mất khoảng 1:30 (khoảng 2 triệu rắn / phút).
Tôi không thực hiện tối ưu hóa, nhưng tôi sẽ không mong đợi nhiều hơn mức tăng x3, vì vậy về cơ bản tôi có thể hy vọng đạt được có thể n = 20 dưới một giờ, hoặc n = 24 dưới một ngày.
Để đạt được hình dạng không thể chịu được đầu tiên (n = 31) sẽ mất từ vài năm đến một thập kỷ, giả sử không có sự cố mất điện.
Đếm hình
Một N kích thước con rắn có N-1 khớp.
Mỗi khớp có thể được đặt thẳng hoặc uốn cong sang trái hoặc phải (3 khả năng).
Số lần gấp có thể là 3 N-1 .
Va chạm sẽ giảm số đó đi một chút, vì vậy con số thực tế gần bằng 2,7 N-1
Tuy nhiên, nhiều nếp gấp như vậy dẫn đến hình dạng giống hệt nhau.
hai hình giống hệt nhau nếu có hoặc là một vòng xoay hoặc một đối xứng mà có thể chuyển đổi một thành kia.
Hãy xác định một phân đoạn là bất kỳ phần thẳng nào của cơ thể gấp.
Ví dụ, một con rắn cỡ 5 được gấp ở khớp thứ 2 sẽ có 2 đoạn (một dài 2 đơn vị và 3 đơn vị thứ hai dài).
Đoạn đầu tiên sẽ được đặt tên là đầu và đuôi cuối cùng .
Theo quy ước, chúng tôi định hướng đầu rắn theo chiều ngang với cơ thể hướng sang phải (như trong hình đầu tiên của OP).
Chúng tôi chỉ định một con số nhất định với một danh sách các độ dài phân khúc đã ký, với độ dài dương cho thấy một nếp gấp phải và âm một nếp gấp trái.
Chiều dài ban đầu là tích cực theo quy ước.
Tách các đoạn và uốn cong
Nếu chúng ta chỉ xem xét các cách khác nhau mà một con rắn có chiều dài N có thể được chia thành các phân đoạn, chúng ta sẽ có một phân vùng giống hệt với các chế phẩm của N.
Sử dụng cùng một thuật toán như được hiển thị trong trang wiki, thật dễ dàng để tạo ra tất cả 2 phân vùng N-1 có thể có của con rắn.
Mỗi phân vùng sẽ lần lượt tạo ra tất cả các nếp gấp có thể bằng cách áp dụng các uốn cong trái hoặc phải cho tất cả các khớp của nó. Một lần gấp như vậy sẽ được gọi là một cấu hình .
Tất cả các phân vùng có thể có thể được biểu diễn bằng một số nguyên của các bit N-1, trong đó mỗi bit đại diện cho sự hiện diện của khớp. Chúng tôi sẽ gọi số nguyên này là một máy phát điện .
Cắt tỉa phân vùng
Bằng cách nhận thấy rằng việc bẻ cong một phân vùng nhất định từ đầu trở xuống tương đương với việc bẻ cong phân vùng đối xứng từ đuôi lên, chúng ta có thể tìm thấy tất cả các cặp phân vùng đối xứng và loại bỏ một trong hai.
Trình tạo phân vùng đối xứng là trình tạo của phân vùng được viết theo thứ tự bit ngược, rất dễ phát hiện và rẻ tiền.
Điều này sẽ loại bỏ gần một nửa các phân vùng có thể, ngoại lệ là các phân vùng có bộ tạo "palindromic" được giữ nguyên bởi đảo ngược bit (ví dụ 00100100).
Chăm sóc các triệu chứng ngang
Với các quy ước của chúng tôi (một con rắn bắt đầu chỉ sang phải), lần uốn đầu tiên được áp dụng ở bên phải sẽ tạo ra một họ các nếp gấp sẽ là đối xứng ngang với các lần uốn khác nhau chỉ ở lần uốn đầu tiên.
Nếu chúng ta quyết định rằng khúc cua đầu tiên sẽ luôn ở bên phải, chúng ta sẽ loại bỏ tất cả các phép đối xứng ngang trong một cú trượt lớn.
Lau dọn các palindromes
Hai vết cắt này là hiệu quả, nhưng không đủ để chăm sóc các palindromes pesky này.
Kiểm tra kỹ lưỡng nhất trong trường hợp chung như sau:
xem xét một cấu hình C với một phân vùng palindromic.
- nếu chúng ta đảo ngược mọi uốn cong trong C, chúng ta sẽ có một biểu tượng ngang của C.
- Nếu chúng ta đảo ngược C (áp dụng các uốn cong từ đuôi lên), chúng ta sẽ có cùng một con số được quay đúng
- nếu cả hai chúng ta đảo ngược và đảo ngược C, chúng ta sẽ có cùng một con số được quay trái.
Chúng tôi có thể kiểm tra mọi cấu hình mới so với 3 cấu hình khác. Tuy nhiên, vì chúng tôi đã chỉ tạo các cấu hình bắt đầu bằng một rẽ phải, chúng tôi chỉ có một biểu tượng có thể kiểm tra:
- C đảo ngược sẽ bắt đầu rẽ trái, do xây dựng không thể nhân đôi
- trong số các cấu hình đảo ngược và đảo ngược, chỉ có một cấu hình sẽ bắt đầu với một rẽ phải.
Đó là cấu hình duy nhất chúng ta có thể nhân đôi.
Loại bỏ trùng lặp mà không có bất kỳ lưu trữ
Cách tiếp cận ban đầu của tôi là lưu trữ tất cả các cấu hình trong một bảng băm lớn, để loại bỏ các bản sao bằng cách kiểm tra sự hiện diện của cấu hình đối xứng được tính toán trước đó.
Nhờ vào bài viết đã nói ở trên, rõ ràng rằng, vì các phân vùng và nếp gấp được lưu trữ dưới dạng bitfield, chúng có thể được so sánh như bất kỳ giá trị số nào.
Vì vậy, để loại bỏ một thành viên của một cặp đối xứng, bạn chỉ cần so sánh cả hai yếu tố và giữ một cách có hệ thống cái nhỏ nhất (hoặc cái lớn nhất, như bạn muốn).
Do đó, kiểm tra một cấu hình để nhân đôi số tiền để tính toán phân vùng đối xứng và nếu cả hai đều giống hệt nhau, thì việc gấp lại. Không có bộ nhớ là cần thiết ở tất cả.
Trật tự thế hệ
Rõ ràng kiểm tra va chạm sẽ là phần tốn nhiều thời gian nhất, vì vậy việc giảm các tính toán này là một công cụ tiết kiệm thời gian chính.
Một giải pháp khả thi là có một "con rắn ragdoll" sẽ bắt đầu ở cấu hình phẳng và được uốn cong dần, để tránh tính toán lại toàn bộ hình dạng con rắn cho từng cấu hình có thể.
Bằng cách chọn thứ tự các cấu hình được kiểm tra, sao cho tối đa một ragdoll được lưu trữ cho mỗi tổng số khớp, chúng ta có thể giới hạn số lượng phiên bản thành N-1.
Tôi sử dụng quét đệ quy sake từ đuôi trở xuống, thêm một khớp duy nhất ở mỗi cấp. Do đó, một phiên bản ragdoll mới được xây dựng dựa trên cấu hình cha, với một uốn cong quảng cáo duy nhất.
Điều này có nghĩa là uốn cong được áp dụng theo thứ tự liên tục, dường như là đủ để tránh tự va chạm trong hầu hết các trường hợp.
Khi tự va chạm được phát hiện, các khúc cua dẫn đến di chuyển vi phạm được áp dụng trong tất cả các đơn đặt hàng có thể cho đến khi tìm thấy gấp hợp pháp hoặc tất cả các kết hợp đã hết.
Kiểm tra tĩnh
Trước cả khi nghĩ về các bộ phận chuyển động, tôi thấy hiệu quả hơn khi kiểm tra hình dạng tĩnh cuối cùng của một con rắn để tự giao nhau.
Điều này được thực hiện bằng cách vẽ con rắn trên lưới. Mỗi điểm có thể được vẽ từ đầu xuống. Nếu có giao điểm tự, ít nhất một cặp điểm sẽ rơi vào cùng một vị trí. Điều này đòi hỏi chính xác N lô cho bất kỳ cấu hình con rắn nào, trong thời gian O (N) không đổi.
Ưu điểm chính của phương pháp này là một mình thử nghiệm tĩnh sẽ chỉ đơn giản chọn các đường dẫn tự tránh hợp lệ trên một mạng vuông, cho phép kiểm tra toàn bộ thuật toán bằng cách ức chế phát hiện va chạm động và đảm bảo chúng tôi tìm thấy số lượng chính xác của các đường dẫn đó.
Kiểm tra động
Khi một con rắn gập quanh một khớp, mỗi đoạn xoay sẽ quét một khu vực có hình dạng là bất cứ thứ gì ngoài tầm thường.
Rõ ràng bạn có thể kiểm tra va chạm bằng cách kiểm tra bao gồm trong tất cả các khu vực quét như vậy riêng lẻ. Kiểm tra toàn cầu sẽ hiệu quả hơn, nhưng với sự phức tạp của các khu vực tôi không thể nghĩ đến (ngoại trừ việc sử dụng GPU để vẽ tất cả các khu vực và thực hiện kiểm tra lần truy cập toàn cầu).
Vì kiểm tra tĩnh đảm nhiệm vị trí bắt đầu và kết thúc của từng phân đoạn, chúng tôi chỉ cần kiểm tra các giao điểm với các cung được quét bởi mỗi phân đoạn xoay.
Sau một cuộc thảo luận thú vị với trichoplax và một chút JavaScript để có được vòng bi của tôi, tôi đã nghĩ ra phương pháp này:
Để thử đặt nó trong một vài từ, nếu bạn gọi
- C tâm quay,
- S một đoạn quay của chiều dài và hướng tùy ý không chứa C ,
- L dòng kéo dài S
- H đường thẳng trực giao với L đi qua C ,
- Tôi là giao điểm của L và H ,
(nguồn: free.fr )
Đối với bất kỳ phân đoạn nào không chứa I , khu vực quét bị ràng buộc bởi 2 cung (và 2 phân đoạn đã được kiểm tra tĩnh).
Nếu tôi nằm trong phân khúc, vòng cung bị quét bởi tôi cũng phải được tính đến.
Điều này có nghĩa là chúng ta có thể kiểm tra từng đoạn không di chuyển so với từng đoạn xoay với 2 hoặc 3 đoạn giao nhau với cung
Tôi đã sử dụng hình học vector để tránh các hàm lượng giác hoàn toàn.
Các hoạt động vector tạo ra mã nhỏ gọn và (tương đối) có thể đọc được.
Giao điểm giữa các phân đoạn đòi hỏi một vectơ dấu phẩy động, nhưng logic phải miễn nhiễm với các lỗi làm tròn.
Tôi tìm thấy giải pháp thanh lịch và hiệu quả này trong một bài đăng diễn đàn tối nghĩa. Tôi tự hỏi tại sao nó không được công bố rộng rãi hơn.
Nó có hoạt động không?
Ức chế phát hiện va chạm động tạo ra các đường dẫn tự tránh chính xác lên tới n = 19, vì vậy tôi khá tự tin rằng bố cục toàn cầu hoạt động.
Phát hiện va chạm động tạo ra kết quả nhất quán, mặc dù việc kiểm tra các khúc cua theo thứ tự khác nhau bị thiếu (hiện tại).
Kết quả là, chương trình đếm những con rắn có thể uốn cong từ đầu xuống (tức là với các khớp được xếp theo thứ tự tăng dần khoảng cách từ đầu).