可达的蛇方向数


11

这个挑战与Snake游戏无关。

想象一条通过画一条水平线形成的2D蛇n。在其身体的整数点处,这条蛇可以将其身体旋转90度。如果我们将蛇的前面定义为最左端,则旋转将移动蛇的后部,而前部将保持原位。通过反复旋转,它可以制成许多不同的蛇形体。

规则

  1. 蛇的身体的一部分不能与另一部分重叠。
  2. 蛇的身体任何部分之间都不得重叠,必须达到最终方向。在此问题中,触摸的两点被视为重叠。
  3. 我认为蛇及其反面是相同的形状。

任务

直到旋转,平移和镜像对称,总共可以制造出多少种蛇形体?

蛇体部分旋转的示例。想象一下n=10,蛇处于直线的起始方向。现在,将点4逆时针旋转90度。我们拿到的蛇从 410垂直趴(该蛇的尾巴)和蛇从04水平放置。蛇现在的身体成一个直角。

这里有一些感谢MartinBüttner的例子。

我们从水平蛇开始。

在此处输入图片说明

现在我们从位置4旋转。

在此处输入图片说明

在此方向旋转之后,我们结束了。

在此处输入图片说明

现在让我们考虑另一条蛇的这种取向。

在此处输入图片说明

现在我们可以看到非法移动,该移动在旋转过程中会引起重叠。

碰撞的例子

得分了

您的分数是n我的计算机在不到一分钟的时间内可以解决该问题的最高分。

当旋转发生时,它将随蛇一起移动一半。我们确实要担心在旋转过程中旋转的任何部分是否会与蛇的一部分重叠。为简单起见,我们可以假定蛇的宽度为零。您只能在蛇形的特定点上沿逆时针方向最多旋转90度。因为,您永远无法将蛇完全折成两段,因为那样会导致在同一方向上的同一点发生两次旋转。

无法制作的形状

不能制作的形状的一个简单例子是大写字母T。一个更复杂的版本是

在此处输入图片说明

(感谢Harald Hanche-Olsen的例子)

在此示例中,所有相邻的水平线和垂直线都相距1。因此,没有从这个位置采取法律行动,并且由于问题是可逆的,因此没有办法从起始位置到达那里。

语言和图书馆

您可以使用任何具有免费编译器/解释器/等的语言。适用于Linux以及任何可免费用于Linux的库。

我的机器 时间将在我的机器上运行。这是在AMD FX-8350八核处理器上的标准ubuntu安装。这也意味着我需要能够运行您的代码。因此,请仅使用易于使用的免费软件,并请提供有关如何编译和运行代码的完整说明。


1
@TApicella感谢您的提问。当我说“发生旋转时,它将使蛇移动一半”,我并不是说要百分之五十。我指的是旋转点之前的部分以及旋转点之后的部分。如果沿着蛇从0旋转,则整个旋转!

2
@TApicella关于您的第二个问题。关键是我给出的职位没有法律上的变动。如果可以到达,则必须有可能通过一系列旋转回到水平的起始方向(与为达到最终方向所需要的旋转方向相反。)您能描述一个合法旋转吗?从这个位置开始?要明确的是,蛇没有长出来。在整个过程中,它始终保持相同的长度。

3
@TApicella听起来像您希望蛇在成长。它的大小是固定的。您从一条长蛇开始,只允许将它的部分折叠90度。从当前位置开始,您将无法进行任何折叠,否则将导致蛇的前一阶段。
马丁·恩德

1
您是否可以多次折叠(来回折叠)?如果可以的话,这将使其变得非常复杂。
randomra

1
@randomra只要您从头开始一直走不超过90度,您的确可以。

Answers:


5

Python 3-临时评分:n = 11(使用PyPy *时,n = 13)

由于第一周没有答案,因此以下是Python中鼓励竞争的示例。我已尝试使其具有合理的可读性,以便可以轻松地识别效率低下的问题,并为其他答案提供思路。

方法

  • 从笔直的蛇开始,找到所有可以一举获得的合法位置。
  • 从尚未确定的职位中查找所有可以合法到达的职位。
  • 重复直到找不到更多的位置,然后返回找到的位置总数。

(现在有了一些doctest,并在我不正确的第一次尝试后断言)

'''
Snake combinations

A snake is represented by a tuple giving the relative orientation at each joint.
A length n snake has n-1 joints.
Each relative orientation is one of the following:

0: Clockwise 90 degrees
1: Straight
2: Anticlockwise 90 degrees

So a straight snake of length 4 has 3 joints all set to 1:

(1, 1, 1)

x increases to the right
y increases upwards

'''


import turtle


def all_coords(state):
    '''Return list of coords starting from (0,0) heading right.'''
    current = (1, 0)
    heading = 0
    coords = [(0,0), (1,0)]
    for item in state:
        heading += item + 3
        heading %= 4
        offset = ((1,0), (0,1), (-1,0), (0,-1))[heading]
        current = tuple(current[i]+offset[i] for i in (0,1))
        coords.append(current)
    return coords


def line_segments(coords, pivot):
    '''Return list of line segments joining consecutive coords up to pivot-1.'''
    return [(coords[i], coords[i+1]) for i in range(pivot+1)]


def rotation_direction(coords, pivot, coords_in_final_after_pivot):
    '''Return -1 if turning clockwise, 1 if turning anticlockwise.'''
    pivot_coord = coords[pivot + 1]
    initial_coord = coords[pivot + 2]
    final_coord = coords_in_final_after_pivot[0]
    initial_direction = tuple(initial_coord[i] - pivot_coord[i] for i in (0,1))
    final_direction = tuple(final_coord[i] - pivot_coord[i] for i in (0,1))
    return (initial_direction[0] * final_direction[1] -
            initial_direction[1] * final_direction[0]
            )


def intersects(arc, line):
    '''Return True if the arc intersects the line segment.'''
    if line_segment_cuts_circle(arc, line):
        cut_points = points_cutting_circle(arc, line)
        if cut_points and cut_point_is_on_arc(arc, cut_points):
            return True


def line_segment_cuts_circle(arc, line):
    '''Return True if the line endpoints are not both inside or outside.'''
    centre, point, direction = arc
    start, finish = line
    point_distance_squared = distance_squared(centre, point)
    start_distance_squared = distance_squared(centre, start)
    finish_distance_squared = distance_squared(centre, finish)
    start_sign = start_distance_squared - point_distance_squared
    finish_sign = finish_distance_squared - point_distance_squared
    if start_sign * finish_sign <= 0:
        return True


def distance_squared(centre, point):
    '''Return the square of the distance between centre and point.'''
    return sum((point[i] - centre[i]) ** 2 for i in (0,1))


def cut_point_is_on_arc(arc, cut_points):
    '''Return True if any intersection point with circle is on arc.'''
    centre, arc_start, direction = arc
    relative_start = tuple(arc_start[i] - centre[i] for i in (0,1))
    relative_midpoint = ((relative_start[0] - direction*relative_start[1])/2,
                         (relative_start[1] + direction*relative_start[0])/2
                         )
    span_squared = distance_squared(relative_start, relative_midpoint)
    for cut_point in cut_points:
        relative_cut_point = tuple(cut_point[i] - centre[i] for i in (0,1))
        spacing_squared = distance_squared(relative_cut_point,
                                           relative_midpoint
                                           )
        if spacing_squared <= span_squared:
            return True


def points_cutting_circle(arc, line):
    '''Return list of points where line segment cuts circle.'''
    points = []
    start, finish = line
    centre, arc_start, direction = arc
    radius_squared = distance_squared(centre, arc_start)
    length_squared = distance_squared(start, finish)
    relative_start = tuple(start[i] - centre[i] for i in (0,1))
    relative_finish = tuple(finish[i] - centre[i] for i in (0,1))
    relative_midpoint = tuple((relative_start[i] +
                               relative_finish[i]
                               )*0.5 for i in (0,1))
    determinant = (relative_start[0]*relative_finish[1] -
                   relative_finish[0]*relative_start[1]
                   )
    determinant_squared = determinant ** 2
    discriminant = radius_squared * length_squared - determinant_squared
    offset = tuple(finish[i] - start[i] for i in (0,1))
    sgn = (1, -1)[offset[1] < 0]
    root_discriminant = discriminant ** 0.5
    one_over_length_squared = 1 / length_squared
    for sign in (-1, 1):
        x = (determinant * offset[1] +
             sign * sgn * offset[0] * root_discriminant
             ) * one_over_length_squared
        y = (-determinant * offset[0] +
             sign * abs(offset[1]) * root_discriminant
             ) * one_over_length_squared
        check = distance_squared(relative_midpoint, (x,y))
        if check <= length_squared * 0.25:
            points.append((centre[0] + x, centre[1] + y))
    return points


def potential_neighbours(candidate):
    '''Return list of states one turn away from candidate.'''
    states = []
    for i in range(len(candidate)):
        for orientation in range(3):
            if abs(candidate[i] - orientation) == 1:
                state = list(candidate)
                state[i] = orientation
                states.append(tuple(state))
    return states


def reachable(initial, final):
    '''
    Return True if final state can be reached in one legal move.

    >>> reachable((1,0,0), (0,0,0))
    False

    >>> reachable((0,1,0), (0,0,0))
    False

    >>> reachable((0,0,1), (0,0,0))
    False

    >>> reachable((1,2,2), (2,2,2))
    False

    >>> reachable((2,1,2), (2,2,2))
    False

    >>> reachable((2,2,1), (2,2,2))
    False

    >>> reachable((1,2,1,2,1,1,2,2,1), (1,2,1,2,1,1,2,1,1))
    False

    '''
    pivot = -1
    for i in range(len(initial)):
        if initial[i] != final[i]:
            pivot = i
            break

    assert pivot > -1, '''
        No pivot between {} and {}'''.format(initial, final)
    assert initial[pivot + 1:] == final[pivot + 1:], '''
        More than one pivot between {} and {}'''.format(initial, final)

    coords_in_initial = all_coords(initial)
    coords_in_final_after_pivot = all_coords(final)[pivot+2:]
    coords_in_initial_after_pivot = coords_in_initial[pivot+2:]
    line_segments_up_to_pivot = line_segments(coords_in_initial, pivot)

    direction = rotation_direction(coords_in_initial,
                                   pivot,
                                   coords_in_final_after_pivot
                                   )

    pivot_point = coords_in_initial[pivot + 1]

    for point in coords_in_initial_after_pivot:
        arc = (pivot_point, point, direction)
        if any(intersects(arc, line) for line in line_segments_up_to_pivot):
            return False
    return True


def display(snake):
    '''Display a line diagram of the snake.

    Accepts a snake as either a tuple:

    (1, 1, 2, 0)

    or a string:

    "1120"

    '''
    snake = tuple(int(s) for s in snake)
    coords = all_coords(snake)

    turtle.clearscreen()
    t = turtle.Turtle()
    t.hideturtle()
    s = t.screen
    s.tracer(0)

    width, height = s.window_width(), s.window_height()

    x_min = min(coord[0] for coord in coords)
    x_max = max(coord[0] for coord in coords)
    y_min = min(coord[1] for coord in coords)
    y_max = max(coord[1] for coord in coords)
    unit_length = min(width // (x_max - x_min + 1),
                      height // (y_max - y_min + 1)
                      )

    origin_x = -(x_min + x_max) * unit_length // 2
    origin_y = -(y_min + y_max) * unit_length // 2

    pen_width = max(1, unit_length // 20)
    t.pensize(pen_width)
    dot_size = max(4, pen_width * 3)

    t.penup()
    t.setpos(origin_x, origin_y)
    t.pendown()

    t.forward(unit_length)
    for joint in snake:
        t.dot(dot_size)
        t.left((joint - 1) * 90)
        t.forward(unit_length)
    s.update()


def neighbours(origin, excluded=()):
    '''Return list of states reachable in one step.'''
    states = []
    for candidate in potential_neighbours(origin):
        if candidate not in excluded and reachable(origin, candidate):
            states.append(candidate)
    return states


def mirrored_or_backwards(candidates):
    '''Return set of states that are equivalent to a state in candidates.'''
    states = set()
    for candidate in candidates:
        mirrored = tuple(2 - joint for joint in candidate)
        backwards = candidate[::-1]
        mirrored_backwards = mirrored[::-1]
        states |= set((mirrored, backwards, mirrored_backwards))
    return states


def possible_snakes(snake):
    '''
    Return the set of possible arrangements of the given snake.

    >>> possible_snakes((1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1))
    {(1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1)}

    '''
    reached = set()
    candidates = set((snake,))

    while candidates:
        candidate = candidates.pop()
        reached.add(candidate)
        new_candidates = neighbours(candidate, reached)
        reached |= mirrored_or_backwards(new_candidates)
        set_of_new_candidates = set(new_candidates)
        reached |= set_of_new_candidates
        candidates |= set_of_new_candidates

    excluded = set()
    final_answers = set()
    while reached:
        candidate = reached.pop()
        if candidate not in excluded:
            final_answers.add(candidate)
            excluded |= mirrored_or_backwards([candidate])

    return final_answers


def straight_derived_snakes(length):
    '''Return the set of possible arrangements of a snake of this length.'''
    straight_line = (1,) * max(length-1, 0)
    return possible_snakes(straight_line)


if __name__ == '__main__':
    import doctest
    doctest.testmod()
    import sys
    arguments = sys.argv[1:]
    if arguments:
        length = int(arguments[0])
    else:
        length = int(input('Enter the length of the snake:'))
    print(len(straight_derived_snakes(length)))

结果

在我的机器上,可以在1分钟内计算出的最长的蛇是长度11(或PyPy *为13)。显然,这只是一个临时评分,直到我们从Lembik的机器中找出官方评分是多少。

为了进行比较,以下是n的前几个值的结果:

 n | reachable orientations
-----------------------------
 0 | 1
 1 | 1
 2 | 2
 3 | 4
 4 | 9
 5 | 22
 6 | 56
 7 | 147
 8 | 388
 9 | 1047
10 | 2806
11 | 7600
12 | 20437
13 | 55313
14 | 148752
15 | 401629
16 | 1078746
17 | MemoryError (on my machine)

如果有任何不正确的地方,请告诉我。

如果您有一个不应该展开的排列示例,则可以使用该功能neighbours(snake)一步找到所有可排列的排列,作为对代码的测试。snake是关节方向的元组(0表示顺时针,1表示笔直,2表示逆时针)。例如(1,1,1)是长度为4(带有3个关节)的直蛇。

可视化

要可视化您所想到的蛇或返回的任何蛇neighbours,可以使用函数display(snake)。与其他函数一样,它接受一个元组,但是由于主程序不使用它,因此不需要快速,因此为了方便起见,它还将接受一个字符串。

display((1,1,2,0)) 相当于 display("1120")

正如Lembik在评论中提到的那样,我的结果与OEIS A037245相同,后者没有考虑旋转过程中的相交。这是因为对于n的前几个值没有区别-可以通过折叠一条直线蛇来获得所有不自相交的形状。可以通过调用neighbours()没有交集就无法展开的蛇来测试代码的正确性。由于它没有邻居,它将仅对OEIS序列起作用,而对这一序列不起作用。我知道的最小例子是Lembik提到的这条长31条蛇,这要感谢David K

(1,1,1,1,2,1,2,1,1,1,1,1,1,2,1,1,1,2,1,1,2,2,1,0,1,0,1,1,1,1)

display('111121211111121112112210101111') 给出以下输出:

没有邻居的最短蛇的形象

提示:如果您调整显示窗口的大小,然后再次调用显示,则蛇将适合新的窗口大小。

我很想听听任何一个没有邻居的简短例子的人的来信。我怀疑最短的此类示例将标记两个序列不同的最小n。


*请注意,n的每个增量大约要花费3倍的时间,因此从n = 11增加到n = 13需要几乎10倍的时间。这就是为什么PyPy比标准Python解释器运行得快得多的原因,只允许将n增加2。


6
如果该评论获得5票赞成,我将考虑添加一个选项以包括可能安排的可视化,以帮助进行分析。
trichoplax


@ Geobits我这次我做对了……
trichoplax


1
@Jakube这可以通过许多方式打开,例如,在命令#1#3#2#4#5#6中。
randomra 2015年

1

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;
}

生成可执行文件

编译时, 我在带有g ++ 4.8的Win7下使用MinGW进行“ Linux”构建,因此不能100%保证可移植性。g++ -O3 -std=c++11

它也可以与标准MSVC2013项目配合使用

通过取消定义NDEBUG,您可以获得算法执行的痕迹和找到的配置的摘要。

表演节目

带有或不带有哈希表的Microsoft编译器都表现得很糟糕:g ++的构建速度提高了3倍

该算法几乎不使用任何内存。

由于冲突检查大致在O(n)中,因此计算时间应该在O(nk n)中,k略小于3。
在我的i3-2100@3.1GHz上,n = 17大约花费了1:30(大约200万蛇/分钟)。

我还没有完成优化,但是我不希望获得超过x3的收益,因此基本上我可以希望在一小时内达到n = 20,或者一天内达到n = 24。

假设没有断电,达到第一个已知的不可弯曲的形状(n = 31)将花费几年到十年的时间。

计算形状

一条N尺寸的蛇具有N-1个关节。
每个关节可以保持笔直或向左或向右弯曲(3种可能性)。
因此,可能的折叠数为3 N-1
碰撞会稍微减少该数量,因此实际数量接近2.7 N-1

然而,许多这样的折叠导致相同的形状。

如果旋转对称可以将一个转换为另一个,则两个形状相同。

让我们将一个定义为折叠主体的任何笔直部分。
例如,在第2个关节处折叠的5号蛇将有2个段(一个2个单位长,第二个3个单位长)。
第一段将被命名为,最后尾巴

按照惯例,我们将蛇的头水平指向,身体指向右侧(如OP中的第一幅图所示)。

我们用给定的图形指定一个带符号的段长度列表,其中正长度表示向右折叠,负长度表示向左折叠。
按照惯例,初始长度为正。

分离段和折弯

如果我们仅考虑将长度为N的蛇分成几段的不同方法,那么最终得到的划分与N 的组成相同。

使用与Wiki页面中所示相同的算法,可以很容易地生成蛇的所有2 N-1个可能的分区

每个分区依次通过向其所有关节施加左右弯曲来生成所有可能的折叠。一种这样的折叠将被称为配置

所有可能的分区都可以由N-1位的整数表示,其中每个位代表关节的存在。我们将这个整数称为生成器

修剪分区

通过注意到从头向下弯曲给定的分区等同于从尾部向上弯曲对称的分区,我们可以找到所有对称的分区,并消除其中的两个。
对称分区的生成器是按相反的位顺序编写的分区生成器,它很容易检测且便宜。

这将消除几乎一半的可能分区,但例外是带有“回文”生成器的分区,这些生成器通过位反转(例如00100100)保持不变。

照顾水平交往

按照我们的约定(一条蛇开始指向右边),在右边施加的第一个折弯将产生一系列折叠,这些折叠将是水平对称的,而仅与第一个折弯不同。

如果我们决定第一个弯头总是在右边,则一口气消除所有水平对称性。

扫清回文

这两个削减是有效的,但不足以照顾这些讨厌的回文。
一般情况下最彻底的检查如下:

考虑具有回文分区的配置C。

  • 如果我们反转 C中的每个折弯,我们最终会得到C的水平对称。
  • 如果我们反转 C(从尾部向上施加折弯),则会得到向右旋转的同一图形
  • 如果我们同时反转C,我们将向左旋转相同的图形。

我们可以对照其他3个配置检查每个新配置。但是,由于我们只生成了右转开始的配置,因此只有一种可能的对称性可以检查:

  • 倒置的C将以左转弯开始,这在构造上是不可能复制的
  • 在反转和反转反转配置中,只有一个会以右转弯开始。
    那是我们可能重复的唯一一种配置。

消除重复而无需任何存储

我最初的方法是将所有配置存储在一个巨大的哈希表中,以通过检查先前计算的对称配置的存在来消除重复项。

由于上述文章,很明显,由于分区和折叠存储为位域,因此可以像任何数值一样对它们进行比较。
因此,要消除一个对称对中的一个成员,您可以简单地比较这两个元素并系统地保留最小的元素(或最大的元素)。

因此,测试配置是否重复就等于计算对称分区,如果两者相同,则折叠。完全不需要内存。

生成顺序

显然,冲突检查将是最耗时的部分,因此减少这些计算将节省大量时间。

可能的解决方案是使用“布娃娃蛇”,该蛇将从平面配置开始并逐渐弯曲,以避免为每种可能的配置重新计算整个蛇的几何形状。

通过选择测试配置的顺序,以便为每个关节总数最多存储一个布娃娃,我们可以将实例数限制为N-1。

我从尾部开始对酒进行递归扫描,在每个级别添加一个关节。因此,新的布娃娃实例将在父配置的基础上构建,并带有一个单独的折弯。

这意味着按顺序施加折弯,这似乎足以在几乎所有情况下避免自碰撞。

当检测到自碰撞时,会以所有可能的顺序施加导致违规移动的弯曲,直到找到合法折叠或用尽所有组合为止。

静态检查

甚至在考虑移动零件之前,我发现测试蛇的静态最终形状以进行自相交会更有效。

这是通过在网格上绘制蛇来完成的。从头向下绘制每个可能的点。如果存在自相交,则至少一对点将落在同一位置。对于任何蛇形配置,这需要精确的N个图,且持续时间为O(N)。

这种方法的主要优点是,仅静态测试就可以简单地在方格上选择有效的自规避路径,从而可以通过禁止动态碰撞检测并确保我们找到此类路径的正确计数来测试整个算法。

动态检查

当一条蛇在一个关节上折叠时,每个旋转的部分都会扫过一个形状不重要的区域。
显然,您可以通过单独测试所有此类扫描区域中的夹杂物来检查碰撞。全局检查会更有效,但考虑到区域的复杂性,我想不出任何办法(除非使用GPU绘制所有区域并执行全局命中检查)。

由于静态测试会考虑每个线段的开始和结束位置,因此我们只需要检查与每个旋转线段扫过的弧线的交点即可。

在与trichoplax和一些JavaScript进行了有趣的讨论以了解我的想法之后,我想到了这种方法:

尝试用几句话说,如果您打电话

  • C旋转中心,
  • S任意长度和方向的旋转段,不包含C
  • L线延长线S
  • H垂直于L的线穿过C
  • 的交点大号ħ

数学
(来源:free.fr

对于任何不包含I的线段,扫描区域均由2个圆弧约束(并且2个线段已由静态检查处理)。

如果属于该段,则还必须考虑由我扫过的弧。

这意味着我们可以通过2或3个弧段交点,对照每个旋转段检查每个静止段

我使用矢量几何来完全避免三角函数。
向量运算产生紧凑的(相对)可读代码。

段到圆弧的交点需要浮点向量,但逻辑应不受舍入误差的影响。
我在一个晦涩的论坛帖子中找到了这种优雅高效的解决方案。我不知道为什么它没有得到更广泛的宣传。

它行得通吗?

禁止动态碰撞检测会产生正确的自我规避路径(最多n = 19),因此我对全局布局的工作很有信心。

动态碰撞检测产生一致的结果,尽管缺少对弯曲顺序的检查(目前)。
结果,该程序计算了可以从头部向下弯曲的蛇(即关节以距头部的距离增加的顺序折叠)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.