도달 가능한 뱀 방향의 수


11

이 도전은 게임 뱀에 관한 것이 아닙니다.

가로 길이의 선을 그려서 형성된 2 차원 뱀을 상상해보십시오 n. 몸을 따라 정수 지점 에서이 뱀은 몸을 90도 회전시킬 수 있습니다. 뱀의 앞면이 맨 왼쪽에 위치하도록 정의하면 회전이 뱀의 뒷면 부분을 움직이고 앞 부분은 그대로 유지됩니다. 반복적 인 회전을 통해 많은 다른 뱀 몸 모양을 만들 수 있습니다.

규칙

  1. 뱀 몸의 한 부분은 다른 부분과 겹칠 수 없습니다.
  2. 뱀의 신체 부분이 겹치지 않고 최종 방향에 도달 할 수 있어야합니다. 이 문제에서 접촉하는 두 점이 겹치는 것으로 계산됩니다.
  3. 나는 뱀과 그 반대가 같은 모양이라고 생각합니다.

직무

회전, 변환 및 거울 대칭까지, 만들 수있는 다른 뱀 몸 모양의 총 수는 얼마입니까?

뱀 몸의 일부의 회전 예. n=10뱀이 직선의 시작 방향에 있다고 상상해보십시오 . 이제 4시계 반대 방향으로 90도 회전 합니다. 우리는에서 뱀을 얻을 410에서합니다 (뱀의 꼬리) 수직으로 누워 뱀 04수평으로 누워. 뱀은 이제 몸에 직각을 가지고 있습니다.

다음은 Martin Büttner 덕분입니다.

우리는 수평 뱀으로 시작합니다.

여기에 이미지 설명을 입력하십시오

이제 우리는 위치 4에서 회전합니다.

여기에 이미지 설명을 입력하십시오

이 방향으로 회전 한 후에 끝납니다.

여기에 이미지 설명을 입력하십시오

이제 다른 뱀의 방향을 생각해 봅시다.

여기에 이미지 설명을 입력하십시오

이제 회전 중에 오버랩이 발생하는 불법 이동을 볼 수 있습니다.

충돌의 예

점수

n내 컴퓨터에서 1 분 이내에 코드를 통해 문제를 해결할 수 있는 최대 점수입니다 .

회전이 발생하면 뱀의 절반이 움직입니다. 회전하는 동안이 부분이 뱀의 일부와 겹칠 수 있는지 걱정할 필요가 있습니다. 간단히하기 위해 뱀의 너비가 0이라고 가정 할 수 있습니다. 뱀의 특정 지점에서만 시계 반대 방향으로 시계 방향으로 최대 90도 회전 할 수 있습니다. 같은 방향으로 같은 지점에서 두 번의 회전을 포함했을 때 뱀을 두 번 접을 수 없습니다.

만들 수없는 모양

만들 수없는 모양의 간단한 예는 capital T입니다. 보다 정교한 버전은

여기에 이미지 설명을 입력하십시오

(이 예제를 위해 Harald Hanche-Olsen에게 감사합니다)

이 예에서 모든 인접한 수평선은 수직선과 1만큼 떨어져 있습니다. 따라서이 위치에서 합법적으로 이동할 수 없으며 문제가 가역적이므로 시작 위치에서 벗어날 수있는 방법이 없습니다.

언어와 라이브러리

자유롭게 사용할 수있는 컴파일러 / 인터프리터 등이있는 모든 언어를 사용할 수 있습니다. Linux 및 Linux 용으로 무료로 제공되는 모든 라이브러리 용.

내 컴퓨터 타이밍이 내 컴퓨터에서 실행됩니다. 이것은 AMD FX-8350 8 코어 프로세서에 표준 우분투 설치입니다. 이것은 또한 코드를 실행할 수 있어야 함을 의미합니다. 결과적으로 쉽게 구할 수있는 무료 소프트웨어 만 사용하고 코드를 컴파일하고 실행하는 방법에 대한 전체 지침을 포함하십시오.


1
@TApicella 질문 해 주셔서 감사합니다. "회전이 발생하면 뱀의 절반이 그와 함께 움직입니다"라고 말했을 때 나는 50 %를 의미하지 않았습니다. 방금 회전 지점 이전의 부분과 그 이후의 부분을 언급했습니다. 뱀을 따라 0에서 회전하면 모든 것을 회전시킵니다!

2
@TApicella 두 번째 질문에 대해. 요점은 내가 준 입장에서 합법적 인 로테이션이 없다는 것입니다. 도달 할 수있는 경우 회전 순서에 따라 수평 시작 방향으로 돌아갈 수 있어야합니다. 이 위치에서? 분명히, 뱀은 자라지 않습니다. 항상 같은 길이를 유지합니다.

3
@TApicella 뱀이 자랄 것으로 예상되는 것 같습니다. 크기는 고정되어 있습니다. 당신은 하나의 긴 뱀으로 시작하고 당신이 할 수있는 것은 그것의 일부를 90도 접는 것입니다. 현재 위치에서 뱀의 이전 단계로 이어질 수있는 접는 부분을 전혀 적용 할 수 없습니다.
Martin Ender

1
당신은 한 번 이상 앞뒤로 접을 수 있습니까? 가능하다면 꽤 복잡해집니다.
randomra

1
@randomra 실제로 90도 이상을 넘어 가지 않는 한 실제로 할 수 있습니다.

답변:


5

Python 3-잠정 점수 : n = 11 (PyPy *의 경우 n = 13)

첫 주에 답변이 없었으므로, 여기에 경쟁을 장려하는 파이썬의 예가 있습니다. 다른 답변에 대한 아이디어를 제공하기 위해 비 효율성을 쉽게 식별 할 수 있도록 합리적으로 읽을 수 있도록 노력했습니다.

접근하다

  • 똑 바른 뱀으로 시작하여 한 번에 합법적으로 도달 할 수있는 모든 위치를 찾으십시오.
  • 해당 위치에서 합법적으로 도달 할 수 있고 아직 식별되지 않은 모든 위치를 찾으십시오.
  • 더 이상 찾을 수 없을 때까지 반복하고 찾은 위치 수를 모두 반환하십시오.

암호

(현재 잘못된 첫 시도 후 일부 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 시퀀스에만 기여하고이 시퀀스에는 기여하지 않습니다. 내가 아는 가장 작은 예는 David K 덕분에 Lembik이 언급 한이 길이의 31 뱀입니다 .

(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

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

실행 파일 빌드

"linux"빌드를 위해 g ++ 4.8과 함께 Win7에서 MinGW를 사용하여 컴파일 하므로 이식성이 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 (약 2 백만) 뱀 / 분).

최적화를 완료하지는 못했지만 x3 이상의 이득을 기대하지 않으므로 기본적으로 1 시간에는 n = 20, 하루에는 n = 24에 도달 할 수 있습니다.

정전이 없다고 가정하면 처음으로 알려진 구부릴 수없는 모양 (n = 31)에 도달하는 데 몇 년에서 10 년이 걸립니다.

모양 계산

N의 크기 뱀 갖는 N-1 관절.
각 조인트는 똑바로 두거나 왼쪽 또는 오른쪽으로 구부릴 수 있습니다 (3 가지 가능성).
따라서 가능한 폴딩 횟수는 3 N-1 입니다.
충돌은 그 수를 다소 줄이므로 실제 수는 2.7 N-1에 가깝습니다.

그러나, 이러한 많은 접힘은 동일한 모양을 초래합니다.

회전 또는 대칭 이 하나가 다른 하나를 변환 할 수 있는 경우 두 모양이 동일합니다 .

접힌 몸체의 직선 부분으로 세그먼트 를 정의합시다 .
예를 들어, 두 번째 조인트에서 접힌 크기 5 뱀은 2 개의 세그먼트 (하나는 2 단위 길이이고 다른 하나는 3 단위 길이)를 갖습니다.
첫 번째 세그먼트는 head 와 마지막 꼬리 로 이름이 지정됩니다 .

관습 적으로 우리는 몸이 오른쪽을 향하도록 뱀의 머리를 수평으로 향하게합니다 (OP 첫 번째 그림과 같이).

양수 길이는 오른쪽 접기를 나타내고 음수는 왼쪽 접기를 나타내는 부호있는 세그먼트 길이 목록으로 지정된 그림을 지정합니다.
초기 길이는 관례 적으로 긍정적입니다.

세그먼트와 벤드 분리

우리는 단지 다른 방법을 고려하면 N 세그먼트로 분할 할 수있는 길이의 뱀, 우리는 동일한 재분할 끝낼 조성물 N.의

위키 페이지에 표시된 것과 동일한 알고리즘을 사용하면 뱀 의 2 개의 N-1 가능한 파티션 을 모두 쉽게 생성 할 수 있습니다 .

각 파티션은 모든 조인트에 왼쪽 또는 오른쪽 굽힘을 적용하여 가능한 모든 접기를 생성합니다. 이러한 폴딩을 구성 이라고 합니다 .

모든 가능한 파티션은 N-1 비트의 정수로 표현 될 수 있으며, 각 비트는 조인트의 존재를 나타냅니다. 이 정수를 제너레이터 라고 부릅니다 .

정리 파티션

헤드 다운에서 특정 파티션을 구부리는 것은 테일 업에서 대칭 파티션을 구부리는 것과 동일하다는 것을 알기 때문에 모든 대칭 파티션을 찾을 수 있고 둘 중 하나를 제거 할 수 있습니다.
대칭 파티션의 생성기는 역 비트 순서로 작성된 파티션의 생성기이며, 이는 쉽고 간단하게 감지 할 수 있습니다.

이렇게하면 가능한 파티션의 거의 절반이 제거되지만 비트 반전에 의해 변경되지 않은 "palindromic"생성기가있는 파티션은 예외입니다 (예 : 00100100).

수평 적 시선을 돌보는

우리의 관습 (뱀이 오른쪽을 가리 키기 시작 함)으로, 오른쪽에 적용된 첫 번째 굽힘은 첫 번째 굽힘에서만 다른 수평 대칭 인 접힘 패밀리를 생성합니다.

첫 번째 굽힘이 항상 오른쪽에 있다고 결정하면 모든 수평 대칭을 한 번에 크게 제거합니다.

회문을 청소

이 두 컷은 효율적이지만 성가신 회문을 돌보기에는 충분하지 않습니다.
일반적인 경우 가장 철저한 검사는 다음과 같습니다.

회문 파티션이있는 구성 C를 고려하십시오.

  • C의 모든 굽힘을 뒤집 으면 수평 대칭 C로 끝납니다.
  • C 를 뒤집 으면 (꼬리부터 굽힘 적용) 같은 그림이 오른쪽으로 회전합니다.
  • 우리가 C를 뒤집고 뒤집 으면, 같은 숫자가 왼쪽으로 회전합니다.

우리는 모든 새로운 구성을 3 개의 다른 구성과 비교할 수있었습니다. 그러나 우회전으로 시작하는 구성 만 이미 생성 했으므로 검사 할 수있는 하나의 대칭 만 있습니다.

  • 거꾸로 된 C는 좌회전으로 시작합니다.
  • 역전 및 역전 구성 중 하나만 오른쪽 회전으로 시작합니다.
    이것이 우리가 복제 할 수있는 유일한 구성입니다.

스토리지없이 중복 제거

내 초기 접근 방식은 모든 구성을 거대한 해시 테이블에 저장하여 이전에 계산 된 대칭 구성이 있는지 확인하여 중복을 제거하는 것이 었습니다.

위에서 언급 한 기사 덕분에 파티션과 폴딩이 비트 필드로 저장되므로 숫자 값과 비교할 수 있음이 분명해졌습니다.
따라서 대칭 쌍의 한 구성원을 제거하기 위해 두 요소를 간단히 비교하고 체계적으로 가장 작은 것 (또는 원하는대로 가장 큰 것)을 유지할 수 있습니다.

따라서, 복제를위한 구성을 테스트하는 것은 대칭 파티션을 계산하고, 둘 다 동일하다면 폴딩이다. 메모리가 전혀 필요하지 않습니다.

세대의 순서

분명히 충돌 검사는 가장 시간이 많이 걸리는 부분이므로 이러한 계산을 줄이는 것이 시간을 절약하는 데 큰 도움이됩니다.

가능한 해결책은 각각의 가능한 구성에 대해 전체 뱀 형상을 재 계산하지 않도록 평평한 구성으로 시작하고 점차 구부러지는 "래그 돌 뱀"을 갖는 것입니다.

구성이 테스트되는 순서를 선택하여 최대 총 조인트 수에 대해 최대 래그 돌이 저장되므로 인스턴스 수를 N-1로 제한 할 수 있습니다.

나는 꼬리 아래에서 술의 재귀 스캔을 사용하여 각 수준에서 단일 조인트를 추가합니다. 따라서 새로운 ragdoll 인스턴스는 단일 구성 굽힘으로 상위 구성 위에 구축됩니다.

이것은 굽힘이 순차적으로 적용된다는 것을 의미하며, 이는 거의 모든 경우에 자체 충돌을 피하기에 충분합니다.

자체 충돌이 감지되면 적법한 접힘이 발견되거나 모든 조합이 소진 될 때까지 문제가되는 이동으로 이어지는 굽힘이 가능한 모든 순서로 적용됩니다.

정적 점검

움직이는 부품에 대해 생각하기 전에 뱀의 정적 최종 모양을 자체 교차로에서 테스트하는 것이 더 효율적이라는 것을 알았습니다.

이것은 뱀을 격자에 그려서 수행됩니다. 각 가능한 지점은 머리부터 아래로 그려집니다. 자체 교차로가 있으면 적어도 한 쌍의 점이 같은 위치에 떨어집니다. 이를 위해서는 일정한 O (N) 시간 동안 모든 뱀 구성에 대해 정확히 N 플롯이 필요합니다.

이 방법의 주요 장점은 정적 테스트만으로도 정사각형 격자에서 유효한 자체 회피 경로를 선택할 수 있다는 것입니다.이 방법은 동적 충돌 감지를 금지하고 이러한 경로의 정확한 개수를 찾음으로써 전체 알고리즘을 테스트 할 수 있습니다.

동적 확인

뱀이 하나의 관절 주위로 접 히면 회전 된 각 세그먼트는 그 모양이 사소한 영역을 쓸어냅니다.
이러한 모든 스윕 영역에 개별적으로 포함을 테스트하여 충돌을 확인할 수 있습니다. 전역 검사는 더 효율적이지만, 내가 생각할 수없는 영역 복잡성을 고려할 때 (GPU를 사용하여 모든 영역을 그리고 전역 적중 검사를 수행하는 경우 제외).

정적 테스트는 각 세그먼트의 시작 및 끝 위치를 처리하므로 각 회전 세그먼트가 스윕 한 와의 교점을 확인하면 됩니다.

trichoplax와 약간의 JavaScript 로 흥미로운 토론을 한 후 베어링을 얻었습니다.

당신이 전화하면 몇 마디로 넣어보십시오

  • C 회전 중심
  • S 함유하지 않는 임의의 아이폰에 방향의 회전 세그먼트 C는 ,
  • L 연장 S 라인
  • H 직교하는 선 L 을 통과 C ,
  • I 의 교차점 LH ,

수학
(출처 : free.fr )

I 가 포함되지 않은 세그먼트의 경우 스윕 영역은 2 개의 호로 제한됩니다 (2 개의 세그먼트는 이미 정적 검사로 처리됨).

경우 내가 세그먼트에 속하는 나는 또한 고려되어야한다에 의해, 아크가 휩쓸었다.

즉, 2 개 또는 3 개의 세그먼트와 아크 교차로 각 회전 세그먼트에 대해 각 움직이지 않는 세그먼트를 확인할 수 있습니다.

삼각 함수를 모두 피하기 위해 벡터 지오메트리를 사용했습니다.
벡터 연산은 작고 상대적으로 읽을 수있는 코드를 생성합니다.

세그먼트 간 교차에는 부동 소수점 벡터가 필요하지만 논리는 반올림 오류에 영향을받지 않아야합니다.
모호한 포럼 게시물 에서이 우아하고 효율적인 솔루션을 찾았습니다. 왜 더 널리 알려지지 않았는지 궁금합니다.

작동합니까?

동적 충돌 감지를 금지하면 올바른 자기 회피 경로가 최대 n = 19로 계산되므로 전역 레이아웃이 작동한다고 확신합니다.

동적 충돌 감지는 다른 순서의 굽힘 검사가 누락되었지만 (현재는) 일관된 결과를 생성합니다.
결과적으로, 프로그램은 머리에서 구부러 질 수있는 뱀을 계산합니다 (예 : 머리에서 거리가 멀어 질수록 접힌 관절).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.