Ulaşılabilir yılan yönlerinin sayısı


11

Bu meydan okuma Snake oyunu ile ilgili değil.

Yatay bir uzunluk çizgisi çizerek oluşan 2d yılanı düşünün n. Vücudu boyunca tamsayı noktalarında, bu yılan vücudunu 90 derece döndürebilir. Yılanın önünü en solda olacak şekilde tanımlarsak, dönüş yılanın arka kısmını hareket ettirir ve ön kısmı yerleştirilir. Tekrarlanan rotasyonlar yaparak birçok farklı yılan vücut şekli oluşturabilir.

kurallar

  1. Yılanın vücudunun bir kısmı diğerinin üzerine binemez.
  2. Yılanın vücudunun herhangi bir kısmı üst üste binmeden son yönelime ulaşmak mümkün olmalıdır. Dokunan iki nokta bu problemin üst üste gelmesi olarak sayılır.
  3. Bir yılanı ve tersini aynı şekil olarak görüyorum.

Görev

Döndürme, çeviri ve ayna simetrisine kadar, yapılabilecek toplam farklı yılan vücut şekli sayısı nedir?

Yılan gövdesinin bir kısmının dönme örneği. Hayal edin n=10ve yılan başlangıçta düz bir çizginin oryantasyonundadır. Şimdi 4saat yönünün tersine 90 derece döndürün. Biz gelen yılan olsun 4için 10gelen (yılan kuyruğu) dikey yalan ve yılan 0için 4yatay yalan. Yılan şimdi vücudunda bir dik açıya sahiptir.

İşte Martin Büttner sayesinde bazı örnekler.

Yatay yılanla başlıyoruz.

resim açıklamasını buraya girin

Şimdi 4. pozisyondan dönüyoruz.

resim açıklamasını buraya girin

Bu yönde rotasyondan sonra son buluruz.

resim açıklamasını buraya girin

Şimdi farklı bir yılanın bu yönelimini ele alalım.

resim açıklamasını buraya girin

Artık rotasyon sırasında çakışmaların olacağı yasadışı bir hareket görebiliriz.

çarpışma örneği

Puan

Puanınız, nkodunuzun sorunu bilgisayarımda bir dakikadan daha kısa bir sürede çözebileceği en yüksek puandır .

Bir rotasyon meydana geldiğinde, yılanın yarısını onunla birlikte hareket ettirir. Döndürülen bu bölümlerden herhangi birinin dönüş sırasında yılanın bir bölümüyle örtüşüp örtüşmeyeceği konusunda endişelenmeliyiz. Basitlik için yılanın genişliği sıfır olduğunu varsayabiliriz. Yılandaki belirli bir noktada saat yönünün tersine saat yönünde 90 dereceye kadar dönebilirsiniz. Çünkü yılanı asla tam olarak ikiye katlayamazsınız, çünkü aynı noktada aynı yönde iki rotasyon içerecektir.

Yapılamayan şekiller

Yapılamaz bir şekle basit bir örnek, başkenttir T. Daha sofistike bir versiyon

resim açıklamasını buraya girin

(Bu örnek için Harald Hanche-Olsen'e teşekkürler)

Bu örnekte, bitişik tüm yatay çizgiler ve dikey çizgiler 1 ayrıdır. Bu nedenle, bu pozisyondan yasal bir hareket yoktur ve sorun tersine çevrilebilir olduğundan, başlangıç ​​pozisyonundan oraya ulaşmanın bir yolu yoktur.

Diller ve kütüphaneler

Serbestçe kullanılabilen bir derleyici / tercüman / vb. Olan herhangi bir dili kullanabilirsiniz. Linux ve Linux için serbestçe bulunan tüm kütüphaneler için.

Benim makine zamanlamaları benim makinede işletilecek. Bu, AMD FX-8350 Sekiz Çekirdekli İşlemciye standart bir ubuntu yüklemesidir. Bu ayrıca kodunuzu çalıştırabilmem gerektiği anlamına gelir. Sonuç olarak, yalnızca kolayca bulunabilen ücretsiz yazılımları kullanın ve lütfen kodunuzu nasıl derleyeceğiniz ve çalıştıracağınızla ilgili tüm talimatları ekleyin.


1
@TApicella Sorular için teşekkürler. "Bir rotasyon gerçekleştiğinde yılanın yarısını onunla birlikte hareket ettirecek" dediğimde yüzde 50 demek istemedim. Sadece dönme noktasından önceki bölümden ve sonraki bölümden bahsediyordum. Yılan boyunca 0'dan döndürürseniz, her şeyi döndürürsünüz!

2
@TApicella İkinci sorunuz hakkında. Mesele şu ki, verdiğim pozisyonda hiçbir yasal rotasyon yok. Ulaşılabilir olsaydı, yatay başlangıç ​​yönüne bir dizi dönüşle geri dönmek mümkün olmalıdır (bitiş yönüne ulaşmak için alacağınızın tersi). Yapabileceğinizi düşündüğünüz bir yasal dönüşü tarif edebilir misiniz? bu pozisyondan? Açıkça söylemek gerekirse, yılan büyümez. Her zaman aynı uzunlukta kalır.

3
@TApicella Yılanın büyümesini beklediğiniz gibi görünüyor. Boyutu sabittir. Bir uzun yılanla başlıyorsunuz ve tek yapmanıza izin verilen kısımları 90 derece katlamak. Mevcut pozisyondan, yılanın önceki bir aşamasına yol açacak hiçbir kıvrımı uygulayamazsınız.
Martin Ender

1
Bir noktada birden fazla (ileri geri) katlanabilir misiniz? Mümkünse bu oldukça karmaşık hale getirir.
randomra

1
@randomra Hiç bir zamandan beri doksan dereceden daha ileri gitmediğin sürece.

Yanıtlar:


5

Python 3 - geçici puan: n = 11 (PyPy * ile n = 13)

İlk haftada cevap alınmadığından, Python'da rekabeti teşvik eden bir örnek. Diğer cevaplar için fikir vermek için verimsizliklerin kolayca tanımlanabilmesi için makul olarak okunabilir hale getirmeye çalıştım.

Yaklaşmak

  • Düz yılan ile başlayın ve tek bir hareketle yasal olarak ulaşılabilecek tüm pozisyonları bulun.
  • Daha önce tanımlanmamış olan bu pozisyonlardan yasal olarak ulaşılabilen tüm pozisyonları bulun.
  • Artık bulunamayana kadar tekrarlayın ve bulunan konumların toplamını döndürün.

kod

(şimdi yanlış ilk denememden sonra bazı doktorlar ve iddialarla)

'''
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)))

Sonuçlar

Makinemde 1 dakikanın altında hesaplanabilen en uzun yılan uzunluk 11 (veya PyPy * ile 13 uzunluk). Lembik'in makinesinden resmi puanın ne olduğunu bulana kadar bu sadece geçici bir puan.

Karşılaştırma için, n'nin ilk birkaç değeri için sonuçlar:

 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)

Bunlardan herhangi birinin yanlış olup olmadığını lütfen bize bildirin.

Açılmaması gereken bir düzenleme örneğiniz varsa neighbours(snake), kodu test etmek için bir adımda erişilebilen düzenlemeleri bulmak için işlevi kullanabilirsiniz . snakebir dizi eklem yönüdür (saat yönünde 0, düz için 1, saat yönünün tersinde 2). Örneğin (1,1,1) 4 uzunluğunda (3 eklemli) düz bir yılandır.

görüntüleme

Aklınızdaki bir yılanı veya geri dönen yılanlardan herhangi birini görselleştirmek neighboursiçin işlevi kullanabilirsiniz display(snake). Bu, diğer işlevler gibi bir demet kabul eder, ancak ana program tarafından kullanılmadığından ve hızlı olması gerekmediğinden, size kolaylık sağlamak için bir dize de kabul eder.

display((1,1,2,0)) eşittir display("1120")

Lembik'in yorumlarda belirttiği gibi, sonuçlarım rotasyon sırasında kavşakları dikkate almayan OEIS A037245 ile aynı . Çünkü n'nin ilk birkaç değeri için fark yoktur - kendiliğinden kesişmeyen tüm şekillere düz bir yılan katlanarak ulaşılabilir. Kodun doğruluğu, neighbours()kesişme olmadan açılamayan bir yılanla çağrılarak test edilebilir. Komşusu olmadığı için sadece OEIS dizisine katkıda bulunacak, buna değil. Farkında olduğum en küçük örnek, David K sayesinde Lembik'in bahsettiği bu uzunluk 31 yılan :

(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') aşağıdaki çıktıyı verir:

Komşuları olmayan en kısa yılan resmi

İpucu: Ekran penceresini yeniden boyutlandırır ve sonra ekranı tekrar ararsanız, yılan yeni pencere boyutuna takılır.

Komşuları olmayan daha kısa bir örneği olan herkesten duymak isterim. Böyle kısa bir örneğin, iki dizinin farklı olduğu en küçük n'yi işaretleyeceğinden şüpheleniyorum.


* Her n artışının kabaca 3 kat daha uzun sürdüğünü unutmayın, bu nedenle n = 11'den n = 13'e çıkmak neredeyse 10 kat gerektirir. Bu nedenle, PyPy, standart Python yorumlayıcısından önemli ölçüde daha hızlı çalışmasına rağmen, n'yi yalnızca 2 arttırmaya izin verir.


6
Bu yorum 5 oy alırsa, analizde yardımcı olması durumunda olası düzenlemelerin görselleştirilmesini dahil etmek için bir seçenek eklemeye bakacağım.
trichoplax


@Geobits Sanırım bu sefer doğru anladım ...
trichoplax


1
@ Jakube Bu birçok yönden açılabilir, örneğin sipariş no. 1 # 3 # 2 # 4 # 5 # 6.
randomra

1

C ++ 11 - neredeyse çalışıyor :)

Bu makaleyi okuduktan sonra , 25 yıl boyunca kare kafeslerde kendinden kaçınma yollarını sayma konusunda daha az karmaşık bir problem üzerinde çalışan bilgelik parçalarını topladım.

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

Yürütülebilir dosyayı oluşturma

Ben ile derlemek için WinG altında MinGW kullanarak "linux" yapıları için g ++ 4.8 ile taşınabilirlik% 100 garanti edilmez.g++ -O3 -std=c++11

Ayrıca standart bir MSVC2013 projesiyle de çalışır (bir çeşit)

Tanımsız olarak NDEBUG, algoritma yürütme izleri ve bulunan yapılandırmaların bir özetini alırsınız.

performanslar

karma tablolar olsun veya olmasın, Microsoft derleyici perişan bir performans sergiliyor: g ++ derlemesi 3 kat daha hızlı .

Algoritma neredeyse hiç bellek kullanmıyor.

Çarpışma kontrolü kabaca O (n) olduğundan, hesaplama süresi O (nk n ) olmalıdır, k 3'ten biraz daha düşüktür.
İ3-2100@3.1GHz'imde n = 17 yaklaşık 1:30 (yaklaşık 2 milyon) alır yılan / dakika).

Optimizasyon yapılmadı, ancak x3 kazancından daha fazlasını beklemeyeceğim, bu yüzden temelde belki bir saatin altında n = 20 veya bir günün altında n = 24'e ulaşmayı umabilirim.

Bilinen ilk dayanılmaz şekle (n = 31) ulaşmak, elektrik kesintisi olmadığı varsayılarak birkaç yıl ile on yıl arasında sürecektir.

Şekil sayma

Bir N boyutu yılan sahip N-1 eklem.
Her eklem düz olarak bırakılabilir veya sola veya sağa doğru bükülebilir (3 olasılık).
Olası katlanma sayısı 3 N- l'dir .
Çarpışmalar bu sayıyı bir miktar azaltacaktır, bu nedenle gerçek sayı 2.7 N-1'e yakındır

Bununla birlikte, bu tür birçok katlama aynı şekillere yol açar.

birini diğerine dönüştürebilen bir döndürme veya simetri varsa iki şekil aynıdır .

Bir segmenti katlanmış gövdenin herhangi bir düz parçası olarak tanımlayalım .
Örneğin, 2. eklemde katlanan 5 numaralı bir yılanın 2 segmenti vardır (bir 2 birim uzunluğunda ve ikinci 3 birim uzunluğunda).
İlk segmente kafa ve son kuyruk olarak adlandırılacaktır .

Geleneksel olarak yılanların başını vücut sağa bakacak şekilde yatay olarak yönlendiririz (OP ilk şekildeki gibi).

Belli bir rakamı, işaretlenmiş segment uzunluklarının bir listesiyle, pozitif uzunluklar sağ katlamayı ve negatif olanlar sol katlamayı gösterir.
İlk uzunluk sözleşmeyle pozitiftir.

Segmentleri ve dirsekleri ayırma

N uzunluğundaki bir yılanın sadece bölümlere ayrılabileceği farklı yolları düşünürsek, N bileşimlerine özdeş bir bölümle sonuçlanırız.

Wiki sayfasında gösterilenle aynı algoritmayı kullanarak, yılanın 2 N-1 olası bölümünün tamamını oluşturmak kolaydır .

Her bölüm sırayla tüm eklemlerine sol veya sağ kıvrımlar uygulayarak olası tüm kıvrımları oluşturur. Böyle bir katlama konfigürasyon olarak adlandırılacaktır .

Tüm olası bölümler, her bitin bir eklemin varlığını temsil ettiği bir N-1 bit tamsayısı ile temsil edilebilir. Bu tamsayıya bir jeneratör diyeceğiz .

Budama bölümleri

Belirli bir bölümün başından aşağı doğru bükülmesinin simetrik bölümü kuyruktan bükmeye eşdeğer olduğunu fark ederek, simetrik bölümlerin tüm çiftlerini bulabilir ve ikisinden birini ortadan kaldırabiliriz.
Simetrik bir bölümün jeneratörü, bölümün ters bit sırasına göre yazılan jeneratörüdür, ki bu tespit edilmesi kolay ve ucuzdur.

Bu, olası bölümlerin neredeyse yarısını ortadan kaldıracaktır, istisnalar, bit tersine çevrilmeden (örneğin 00100100) değiştirilmeyen "palindromik" jeneratörlere sahip bölümlerdir.

Yatay simetrilerin bakımı

Sözleşmelerimizle (bir yılan sağa işaret etmeye başlar), sağa uygulanan ilk viraj, sadece ilk virajdan farklı olanlardan yatay simetrik olacak bir katlama ailesi üretecektir.

İlk virajın her zaman sağda olacağına karar verirsek, tüm yatay simetrileri tek bir büyük vuruşta ortadan kaldırırız.

Palindromları silmek

Bu iki kesim etkilidir, ancak bu sinir bozucu palindromlarla ilgilenmek için yeterli değildir.
Genel durumda en kapsamlı kontrol aşağıdaki gibidir:

palindromik bölmeli C konfigürasyonunu düşünün.

  • Biz ise ters C her dirsek, C. yatay bir simetrik ile sona
  • C'yi tersine çevirirsek (kuyruktan kıvrımlar uygularsak ), aynı rakamın sağa döndürülmesini sağlarız
  • C'yi ters çevirip tersine çevirirsek, aynı rakamı sola döndürürüz.

Her yeni yapılandırmayı diğer 3 tanesine göre kontrol edebiliriz. Bununla birlikte, zaten doğru bir dönüşle başlayan konfigürasyonları ürettiğimizden, kontrol etmek için sadece bir olası simetrimiz var:

  • ters C, sola dönerek başlayacak, bu da inşaat ile çoğaltılması imkansız
  • tersine çevrilmiş ve ters çevrilmiş konfigürasyonlardan sadece bir tanesi sağa dönüşle başlayacaktır.
    Çoğaltabileceğimiz tek yapılandırma budur.

Depolama alanı olmadan kopyaları ortadan kaldırma

İlk yaklaşımım, tüm yapılandırmaları büyük bir karma tabloda depolamak, daha önce hesaplanmış bir simetrik yapılandırmanın varlığını kontrol ederek kopyaları ortadan kaldırmaktı.

Bahsedilen makale sayesinde, bölümler ve katlamalar bit alanları olarak saklandığından, herhangi bir sayısal değer gibi karşılaştırılabilecekleri anlaşıldı.
Bu nedenle, simetrik bir çiftin bir üyesini ortadan kaldırmak için her iki öğeyi de basitçe karşılaştırabilir ve en küçük olanı (veya istediğiniz gibi en büyük olanı) sistematik olarak tutabilirsiniz.

Bu nedenle, çoğaltma için bir konfigürasyonun test edilmesi, simetrik bölümün hesaplanması ve her ikisi de aynı ise katlamanın hesaplanması anlamına gelir. Hiç bellek gerekmez.

Üretim sırası

Açıkça çarpışma kontrolü en çok zaman alan kısım olacaktır, bu nedenle bu hesaplamaların azaltılması büyük bir zaman tasarrufu sağlar.

Olası bir çözüm, olası her yapılandırma için tüm yılan geometrisinin yeniden hesaplanmasını önlemek için düz bir konfigürasyonda başlayacak ve kademeli olarak bükülecek bir "ragdoll yılanına" sahip olmaktır.

Konfigürasyonların test edilme sırasını seçerek, her bir toplam eklem sayısı için en fazla bir ragdoll depolanacak şekilde, örnek sayısını N-1 ile sınırlayabiliriz.

Her seviyeden tek bir eklem ekleyerek, kuyruktan aşağıya tekrarlayan bir tarama kullanıyorum. Böylece, tek bir ek viraj ile üst konfigürasyonun üzerine yeni bir ragdoll örneği oluşturulur.

Bu, virajların sıralı bir sırayla uygulandığı, neredeyse tüm durumlarda kendi kendine çarpışmayı önlemek için yeterli olduğu anlamına gelir.

Kendiliğinden çarpışma tespit edildiğinde, rahatsız edici harekete neden olan kıvrımlar yasal katlama bulunana veya tüm kombinasyonlar tükenene kadar olası tüm siparişlerde uygulanır.

Statik kontrol

Hareketli parçaları düşünmeden önce, bir yılanın statik son şeklini kendi kendine kavşaklar için test etmenin daha verimli olduğunu gördüm.

Bu, yılanı bir ızgaraya çizerek yapılır. Olası her nokta baştan aşağıya çizilir. Kendi kendine bir kavşak varsa, aynı yere en az bir çift nokta düşecektir. Bu, herhangi bir yılan konfigürasyonu için sabit bir O (N) süresi için tam olarak N grafiği gerektirir.

Bu yaklaşımın temel avantajı, statik testin tek başına, kare bir kafes üzerinde geçerli kendinden kaçınan yolları seçmesidir; bu, dinamik çarpışma algılamasını engelleyerek ve bu tür yolların doğru sayısını bulduğumuzdan emin olarak tüm algoritmayı test etmeyi sağlar.

Dinamik kontrol

Bir yılan bir eklemin etrafında katlandığında, her döndürülmüş segment, şekli önemsiz olan bir alanı süpürür.
Açıkça, tüm bu süpürülmüş alanlardaki dahil edilmeyi tek tek test ederek çarpışmaları kontrol edebilirsiniz. Küresel bir kontrol daha verimli olacaktır, ancak düşünemediğim alanların karmaşıklığı göz önüne alındığında (belki de tüm alanları çizmek ve global bir isabet kontrolü yapmak için bir GPU kullanmak dışında).

Statik test, her bir segmentin başlangıç ​​ve bitiş konumlarına baktığından, her dönen segment tarafından süpürülen yaylarla kesişimleri kontrol etmemiz gerekir .

Yataklarımı almak için trichoplax ve biraz JavaScript ile ilginç bir tartışmadan sonra , bu yöntemi buldum:

Birkaç kelimeyle ifade etmeye çalışmak için,

  • C dönme merkezi,
  • S , keyfi uzunluk ve yönlerde C içermeyen dönen bir segmenttir ,
  • L çizgi uzatma S
  • H , C'den geçen L'ye dik olan çizgi ,
  • Ben L ve H'nin kesiştiği yer ,

Matematik
(kaynak: free.fr )

I içermeyen herhangi bir segment için , süpürülen alan 2 yay ile bağlanır (ve statik kontrolle zaten ilgilenilen 2 segment).

Eğer ben segmenti içine düşen ben de dikkate alınmalıdır tarafından, yay süpürüldü.

Bu, hareketsiz olan her bir parçayı 2 veya 3 ark-kavisli kavşak ile her bir dönen parçaya göre kontrol edebileceğimiz anlamına gelir

Trigonometrik fonksiyonları tamamen önlemek için vektör geometrisini kullandım.
Vektör işlemleri, kompakt ve (nispeten) okunabilir kod üretir.

Segment-ark kavşağı bir kayan nokta vektörü gerektirir, ancak mantık yuvarlama hatalarına karşı bağışık olmalıdır.
Bu zarif ve etkili çözümü belirsiz bir forum gönderisinde buldum. Acaba neden daha yaygın bir şekilde duyurulmuyor.

Çalışıyor mu?

Dinamik çarpışma algılamanın engellenmesi, n = 19'a kadar doğru kendinden kaçınma yollarını üretir, bu nedenle küresel düzen çalışmalarından oldukça eminim.

Dinamik çarpışma tespiti tutarlı sonuçlar üretir, ancak farklı sıradaki virajların kontrolü eksiktir (şimdilik).
Sonuç olarak, program başından aşağıya doğru bükülebilen yılanları sayar (yani kafadan artan mesafeye göre katlanmış eklemlerle).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.