จำนวนการหมุนของงูที่สามารถเข้าถึงได้


11

ความท้าทายนี้ไม่ได้เกี่ยวกับเกมของงู

ลองนึกภาพงู 2d nที่เกิดขึ้นจากการวาดเส้นแนวนอนที่มีความยาว งูนี้สามารถหมุนร่างของมันได้ 90 องศา หากเรากำหนดด้านหน้าของงูให้อยู่ทางซ้ายสุดเพื่อเริ่มต้นการหมุนจะย้ายส่วนหลังของงูและส่วนหน้าจะคงอยู่ ด้วยการหมุนวนซ้ำ ๆ มันสามารถสร้างรูปร่างของงูที่แตกต่างกันได้มากมาย

กฎระเบียบ

  1. ส่วนหนึ่งของร่างกายของงูไม่สามารถทับซ้อนกันได้
  2. จะต้องมีความเป็นไปได้ที่จะเข้าถึงการปฐมนิเทศครั้งสุดท้ายโดยไม่มีส่วนใดส่วนหนึ่งของร่างกายของงูทับซ้อนกัน จุดสองจุดที่สัมผัสถูกนับว่าทับซ้อนกันในปัญหานี้
  3. ฉันถือว่างูและสิ่งที่ตรงกันข้ามเป็นรูปร่างเดียวกัน

งาน

ขึ้นอยู่กับการหมุนการแปลและกระจกสมมาตรรูปร่างของงูที่แตกต่างกันทั้งหมดที่สามารถทำได้คืออะไร?

ตัวอย่างของการหมุนของส่วนของร่างกายของงู ลองนึกภาพn=10และงูอยู่ในทิศทางเริ่มต้นของเส้นตรง ตอนนี้หมุนที่จุด490 องศาทวนเข็มนาฬิกา เราได้รับงูจาก 4ไปที่10(หางของงู) นอนในแนวตั้งและงูจาก0การ4นอนในแนวนอน ตอนนี้งูมีมุมฉากอยู่หนึ่งตัว

นี่คือตัวอย่างบางส่วนขอบคุณ Martin Büttner

เราเริ่มต้นด้วยงูแนวนอน

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้เราหมุนจากตำแหน่ง 4

ป้อนคำอธิบายรูปภาพที่นี่

เราจบลงหลังจากการหมุนในทิศทางนี้

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้ให้เราพิจารณาการวางแนวของงูตัวอื่น

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้เราสามารถเห็นการเคลื่อนไหวที่ผิดกฎหมายซึ่งจะมีการทับซ้อนเกิดขึ้นในระหว่างการหมุน

ตัวอย่างของการชนกัน

คะแนน

คะแนนของคุณมากที่สุดnซึ่งรหัสของคุณสามารถแก้ไขปัญหาได้ในเวลาไม่ถึงหนึ่งนาทีในคอมพิวเตอร์ของฉัน

เมื่อมีการหมุนเกิดขึ้นมันจะเลื่อนงูไปครึ่งหนึ่งด้วย เราต้องกังวลว่าส่วนใดส่วนหนึ่งที่หมุนอยู่นี้อาจซ้อนทับส่วนหนึ่งของงูระหว่างการหมุน เพื่อความง่ายเราสามารถสมมติว่างูมีความกว้างเป็นศูนย์ คุณสามารถหมุนที่จุดใดจุดหนึ่งในงูได้สูงสุด 90 องศาตามเข็มนาฬิกาของทวนเข็มนาฬิกา สำหรับคุณไม่สามารถพับงูเป็นสองเท่าได้อย่างเต็มที่เนื่องจากจะมีการหมุนสองรอบที่จุดเดียวกันในทิศทางเดียวกัน

รูปร่างที่ไม่สามารถทำได้

Tตัวอย่างง่ายๆของรูปร่างที่ไม่สามารถทำให้เป็นทุน รุ่นที่ซับซ้อนมากขึ้นคือ

ป้อนคำอธิบายรูปภาพที่นี่

(ขอบคุณ Harald Hanche-Olsen สำหรับตัวอย่างนี้)

ในตัวอย่างนี้เส้นแนวนอนทั้งหมดที่อยู่ติดกันจะมี 1 เส้นแยกกันเหมือนกับเส้นแนวตั้ง ดังนั้นจึงไม่มีการย้ายที่ถูกกฎหมายจากตำแหน่งนี้และเนื่องจากปัญหาสามารถย้อนกลับได้ดังนั้นจึงไม่มีวิธีการเดินทางจากตำแหน่งเริ่มต้น

ภาษาและห้องสมุด

คุณสามารถใช้ภาษาใดก็ได้ที่มีคอมไพเลอร์ / ล่าม / อื่น ๆ สำหรับ Linux และไลบรารี่ใด ๆ ที่มีให้ใช้งานบน Linux ได้อย่างอิสระ

เครื่องของฉัน การจับเวลาจะทำงานบนเครื่องของฉัน นี่คือการติดตั้ง Ubuntu มาตรฐานบนโปรเซสเซอร์ AMD FX-8350 Eight-Core นี่ก็หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้ ดังนั้นโปรดใช้ซอฟต์แวร์ฟรีที่มีให้ง่ายและโปรดรวมคำแนะนำทั้งหมดในการรวบรวมและเรียกใช้รหัสของคุณ


1
@TApella ขอบคุณสำหรับคำถาม เมื่อฉันพูดว่า "เมื่อมีการหมุนเกิดขึ้นมันจะย้ายงูไปครึ่งหนึ่งด้วย" ฉันไม่ได้คิด 50 เปอร์เซ็นต์ ฉันแค่อ้างถึงชิ้นส่วนก่อนจุดหมุนและชิ้นส่วนหลังจากนั้น หากคุณหมุนจาก 0 ไปตามงูคุณหมุนสิ่งทั้งหมด!

2
@TApicella เกี่ยวกับคำถามที่สองของคุณ ประเด็นก็คือไม่มีการหมุนตามกฎหมายจากตำแหน่งที่ฉันให้ หากสามารถเข้าถึงได้คุณจะต้องกลับไปที่การวางแนวเริ่มต้นในแนวนอนด้วยลำดับการหมุน (กลับด้านที่คุณจะต้องไปถึงการวางแนวจบ) คุณสามารถอธิบายการหมุนตามกฎหมายหนึ่งครั้งที่คุณคิดว่าคุณสามารถทำได้ จากตำแหน่งนี้ เพื่อความชัดเจนงูจะไม่เติบโต มันคงความยาวเท่ากันตลอด

3
@Tapella ดูเหมือนว่าคุณคาดหวังว่างูจะเติบโต ขนาดมันคงที่แล้ว คุณเริ่มต้นด้วยงูยาวหนึ่งตัวและทั้งหมดที่คุณได้รับอนุญาตให้ทำคือพับส่วนของมัน 90 องศา จากตำแหน่งปัจจุบันคุณไม่สามารถพับทั้งหมดที่จะนำไปสู่ขั้นตอนก่อนหน้าของงู
Martin Ender

1
คุณสามารถพับที่จุดมากกว่าหนึ่งครั้ง (ไปมา)? หากคุณสามารถทำให้มันค่อนข้างซับซ้อน
Randomra

1
@ randomra แน่นอนคุณสามารถตราบใดที่คุณไม่เคยไปไกลกว่าเก้าสิบองศาจากตรงไป

คำตอบ:


5

Python 3 - คะแนนชั่วคราว: n = 11 (n = 13 พร้อม PyPy *)

เนื่องจากไม่มีคำตอบในสัปดาห์แรกนี่เป็นตัวอย่างใน Python เพื่อส่งเสริมการแข่งขัน ฉันพยายามทำให้อ่านได้อย่างมีเหตุผลเพื่อให้สามารถระบุความไร้ประสิทธิภาพได้อย่างง่ายดายเพื่อให้แนวคิดสำหรับคำตอบอื่น ๆ

เข้าใกล้

  • เริ่มต้นด้วยงูตรงและค้นหาตำแหน่งทั้งหมดที่สามารถเข้าถึงได้อย่างถูกกฎหมายในการย้าย
  • ค้นหาตำแหน่งทั้งหมดที่สามารถเข้าถึงได้อย่างถูกกฎหมายจากตำแหน่งเหล่านั้นซึ่งยังไม่ได้ระบุ
  • ทำซ้ำจนกว่าจะไม่พบอีกแล้วส่งคืนจำนวนตำแหน่งที่พบทั้งหมด

รหัส

(ตอนนี้มีการสอนและยืนยันหลังจากพยายามครั้งแรกของฉันไม่ถูกต้อง)

'''
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 (หรือความยาว 13 พร้อม PyPy *) เห็นได้ชัดว่ามันเป็นเพียงคะแนนชั่วคราวจนกว่าเราจะพบว่าคะแนนอย่างเป็นทางการมาจากเครื่องของ 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คือ tuple ของทิศทางร่วม (0 สำหรับเข็มนาฬิกา, 1 สำหรับตรง, 2 สำหรับทวนเข็มนาฬิกา) ตัวอย่างเช่น (1,1,1) เป็นงูตรงความยาว 4 (มี 3 ข้อต่อ)

การแสดง

หากต้องการเห็นภาพงูที่คุณมีอยู่ในใจหรืองูที่ถูกส่งกลับโดยneighboursคุณสามารถใช้ฟังก์ชั่นdisplay(snake)นี้ได้ สิ่งนี้ยอมรับ tuple เหมือนกับฟังก์ชั่นอื่น ๆ แต่เนื่องจากมันไม่ได้ถูกใช้โดยโปรแกรมหลักดังนั้นจึงไม่จำเป็นต้องรวดเร็วมันจึงยอมรับสตริงเพื่อความสะดวกของคุณ

display((1,1,2,0)) เทียบเท่ากับ display("1120")

ตามที่ Lembik กล่าวถึงในความคิดเห็นผลลัพธ์ของฉันเหมือนกับOEIS A037245ซึ่งไม่ได้คำนึงถึงจุดตัดระหว่างบัญชีในระหว่างการหมุน นี่เป็นเพราะในสองสามค่าแรกของ n ไม่มีความแตกต่าง - รูปร่างทั้งหมดที่ไม่สามารถตัดกันด้วยตนเองสามารถเข้าถึงได้โดยการพับงูตรง ความถูกต้องของรหัสสามารถทดสอบได้โดยการโทรหาneighbours()งูที่ไม่สามารถกางออกได้โดยไม่ต้องแยก เนื่องจากไม่มีเพื่อนบ้านมันจะมีส่วนร่วมในลำดับ OEIS เท่านั้น ตัวอย่างที่เล็กที่สุดที่ฉันรู้คืองูตัวยาว 31 ตัวที่ Lembik พูดถึงขอบคุณ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 อนุญาตให้เพิ่ม n 2 เท่านั้นแม้ว่ามันจะทำงานได้เร็วกว่าล่าม Python มาตรฐานมาก


6
หากความคิดเห็นนี้ได้รับ 5 upvotes ฉันจะดูการเพิ่มตัวเลือกเพื่อรวมการแสดงภาพของการเตรียมการที่เป็นไปได้ในกรณีที่ช่วยในการวิเคราะห์
trichoplax


@Geobits ฉันคิดว่าฉันได้มันขวาเวลานี้ ...
Trichoplax

ตอนนี้ดูเหมือนว่าจะเป็นoeis.org/A037245จากamericanscientist.org/issues/pub/how-to-avoid-yourself !

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

สร้างปฏิบัติการ

รวบรวมกับ ฉันใช้ MinGW ภายใต้ Win7 กับ g ++ 4.8 สำหรับการสร้าง "linux" ดังนั้นการพกพาจึงไม่รับประกัน 100%g++ -O3 -std=c++11

นอกจากนี้ยังใช้งานได้ (เรียงลำดับ) กับโครงการ MSVC2013 มาตรฐาน

โดย Undefining NDEBUGคุณจะได้รับร่องรอยของการดำเนินการขั้นตอนวิธีการและการสรุปของการกำหนดค่าพบ

การแสดง

มีหรือไม่มีตารางแฮช, ไมโครซอฟท์ดำเนินการคอมไพเลอร์อย่างน่าสังเวช: กรัม ++ สร้างเป็น3 ครั้งได้เร็วขึ้น

อัลกอริทึมไม่ได้ใช้หน่วยความจำเลย

เนื่องจากการตรวจสอบการชนกันของข้อมูลนั้นอยู่ใน O (n) เวลาในการคำนวณควรอยู่ใน O (nk n ) โดยที่ k ต่ำกว่า 3 เล็กน้อย
ใน i3-2100@3.1GHz ของฉัน n = 17 ใช้เวลาประมาณ 1:30 (ประมาณ 2 ล้าน) งู / นาที)

ฉันไม่ได้ทำการปรับให้เหมาะสม แต่ฉันจะไม่คาดหวังว่าจะได้รับมากกว่า x3 ดังนั้นโดยทั่วไปฉันสามารถหวังว่าจะถึง n = 20 ภายใต้หนึ่งชั่วโมงหรือ n = 24 ภายใต้หนึ่งวัน

การเข้าถึงรูปร่างที่ไม่สามารถทนทานได้ที่รู้จักกันเป็นครั้งแรก (n = 31) จะใช้เวลาสองถึงสามปีถึงหนึ่งทศวรรษโดยไม่มีไฟฟ้าดับ

การนับรูปร่าง

NขนาดงูมีN-1ข้อต่อ
ข้อต่อแต่ละข้อสามารถซ้ายหรือโค้งงอไปทางซ้ายหรือขวา (3 ความเป็นไปได้)
จำนวนพับที่เป็นไปได้ดังนั้นจึงเป็น 3 N-1
การชนจะลดจำนวนลงบ้างดังนั้นจำนวนจริงจึงใกล้เคียงกับ 2.7 N-1

อย่างไรก็ตามการพับหลายครั้งทำให้มีรูปร่างเหมือนกัน

รูปร่างสองรูปเหมือนกันหากมีการหมุนหรือมีสัญลักษณ์ที่สามารถแปลงเป็นรูปร่างอื่นได้

ลองกำหนดกลุ่มเป็นส่วนตรง ๆ ของส่วนที่พับ
ตัวอย่างเช่นงูขนาด 5 ที่พับที่ข้อต่อ 2 จะมี 2 ส่วน (ยาว 1 หน่วยและยาว 3 วินาที)
ส่วนแรกจะถูกตั้งชื่อตามหัวและท้ายสุด

โดยการประชุมเราวางหัวงูในแนวนอนโดยหันลำตัวไปทางขวา (เช่นในรูปแรกของ OP)

เรากำหนดรูปที่กำหนดด้วยรายการความยาวของส่วนที่เซ็นชื่อพร้อมความยาวเป็นบวกที่ระบุการพับด้านขวาและลบในทางลบ
ความยาวเริ่มต้นเป็นค่าบวกโดยการประชุม

แยกส่วนและโค้ง

หากเราพิจารณาเพียงวิธีที่แตกต่างกันงูที่มีความยาว N สามารถแบ่งออกเป็นส่วน ๆ ได้เราจะสิ้นสุดด้วยการแบ่งส่วนเหมือนกับองค์ประกอบของ N

การใช้อัลกอริธึมแบบเดียวกับที่แสดงในหน้า wiki ทำให้ง่ายต่อการสร้างพาร์ติชั่นที่เป็นไปได้ทั้งหมดของงู2 N-1

แต่ละพาร์ติชั่นจะสร้างรอยพับที่เป็นไปได้ทั้งหมดโดยการโค้งซ้ายหรือขวากับข้อต่อทั้งหมด หนึ่งพับดังกล่าวจะถูกเรียกว่าการกำหนดค่า

พาร์ติชันที่เป็นไปได้ทั้งหมดสามารถถูกแทนด้วยจำนวนเต็มของบิต N-1 โดยที่แต่ละบิตแทนการมีอยู่ของรอยต่อ เราจะเรียกสิ่งนี้ว่าจำนวนเต็มกำเนิด

พาร์ติชันการตัดแต่งกิ่ง

โดยการสังเกตว่าการโค้งงอพาร์ติชันที่กำหนดจากหัวลงนั้นเทียบเท่ากับการดัดฉากกั้นจากหางขึ้นเราสามารถหาพาร์ติชัน symetrical คู่และกำจัดหนึ่งในสอง
ตัวกำเนิดของพาร์ติชัน symetrical เป็นตัวกำเนิดของพาร์ติชันที่เขียนในลำดับบิตย้อนกลับซึ่งง่ายต่อการตรวจสอบและราคาถูก

สิ่งนี้จะกำจัดเกือบครึ่งหนึ่งของพาร์ติชั่นที่เป็นไปได้ข้อยกเว้นการเป็นพาร์ติชั่นด้วยเครื่องกำเนิดไฟฟ้า "palindromic" ที่ไม่เปลี่ยนแปลงโดยการสลับบิต (เช่น 00100100)

การดูแล symetries แนวนอน

ด้วยการประชุมของเรา (งูเริ่มชี้ไปทางขวา) การโค้งงอครั้งแรกที่ใช้ทางด้านขวาจะทำให้เกิดรอยพับในแนวนอนซึ่งจะเป็นแนวนอนแนวตั้งจากแนวโค้งที่แตกต่างจากโค้งแรกเท่านั้น

หากเราตัดสินใจว่าการโค้งงอครั้งแรกจะอยู่ทางด้านขวาเราจะกำจัด symetrics แนวนอนทั้งหมดในการปัดครั้งใหญ่

การถู palindromes

การตัดสองอย่างนี้มีประสิทธิภาพ แต่ไม่เพียงพอที่จะดูแล palindromes ที่น่ารำคาญเหล่านี้
การตรวจสอบอย่างละเอียดที่สุดในกรณีทั่วไปมีดังนี้:

พิจารณาการกำหนดค่า C ด้วยพาร์ติชัน palindromic

  • ถ้าเรากลับโค้งทุกอันใน C เราจะได้สมมาตรแนวนอนของ C
  • ถ้าเราย้อนกลับ C (ใช้โค้งจากหางขึ้นไป) เราจะได้ตัวเลขที่หมุนเหมือนเดิม
  • ถ้าเราทั้งคู่กลับด้านและกลับด้าน C เราจะได้ตัวเลขที่หมุนไปทางซ้ายเหมือนกัน

เราสามารถตรวจสอบการกำหนดค่าใหม่กับ 3 รายการอื่น ๆ อย่างไรก็ตามเนื่องจากเราสร้างการกำหนดค่าเริ่มต้นด้วยการเลี้ยวขวาเท่านั้นเราจึงมีการตรวจสอบความเป็นไปได้เพียงอย่างเดียว:

  • ฤCษี C จะเริ่มต้นด้วยการเลี้ยวซ้ายซึ่งเป็นไปไม่ได้ที่จะสร้างซ้ำ
  • ออกจากการกำหนดค่าย้อนกลับและกลับตรงกันข้ามเพียงหนึ่งจะเริ่มต้นด้วยการเลี้ยวขวา
    นั่นคือการกำหนดค่าเดียวเท่านั้นที่เราสามารถทำซ้ำได้

กำจัดสิ่งที่ซ้ำกันโดยไม่มีที่เก็บข้อมูลใด ๆ

วิธีการเริ่มต้นของฉันคือการจัดเก็บการกำหนดค่าทั้งหมดในตารางแฮชขนาดใหญ่เพื่อกำจัดรายการที่ซ้ำกันโดยการตรวจสอบสถานะของการกำหนดค่า symetric ที่คำนวณก่อนหน้านี้

ต้องขอบคุณบทความดังกล่าวข้างต้นทำให้เห็นได้ชัดว่าเนื่องจากพาร์ติชั่นและการพับจะถูกเก็บไว้เป็นบิตฟิลด์พวกเขาสามารถเปรียบเทียบได้เหมือนกับค่าตัวเลขใด ๆ
ดังนั้นในการกำจัดสมาชิกของคู่สมมาตรคุณสามารถเปรียบเทียบทั้งสององค์ประกอบและรักษาสมาชิกที่เล็กที่สุดอย่างเป็นระบบ (หรือสมาชิกที่ยิ่งใหญ่ที่สุดเท่าที่คุณต้องการ)

ดังนั้นการทดสอบการกำหนดค่าสำหรับจำนวนที่ซ้ำกันเพื่อคำนวณพาร์ติชัน symetric และหากทั้งสองเหมือนกันการพับ ไม่จำเป็นต้องใช้หน่วยความจำเลย

ลำดับของรุ่น

การตรวจสอบการชนอย่างชัดเจนจะเป็นส่วนที่ใช้เวลามากที่สุดดังนั้นการลดการคำนวณเหล่านี้จึงเป็นการประหยัดเวลาที่สำคัญ

ทางออกที่เป็นไปได้คือมี "ragdoll snake" ที่จะเริ่มในการกำหนดค่าแบบแบนและค่อย ๆ งอเพื่อหลีกเลี่ยงการคำนวณรูปทรงเรขาคณิตของงูทั้งหมดสำหรับการกำหนดค่าที่เป็นไปได้แต่ละครั้ง

โดยการเลือกลำดับการทดสอบการกำหนดค่าเพื่อให้ ragdoll ส่วนใหญ่ถูกเก็บไว้สำหรับจำนวนข้อต่อแต่ละข้อเราสามารถ จำกัด จำนวนอินสแตนซ์เป็น N-1

ฉันใช้การสแกนซ้ำของสาเกจากหางลงโดยเพิ่มรอยต่อเดี่ยวในแต่ละระดับ ดังนั้นอินสแตนซ์ ragdoll ใหม่จะถูกสร้างขึ้นที่ด้านบนของการกำหนดค่าหลักด้วยโค้งงอ aditional เดียว

ซึ่งหมายความว่าการโค้งถูกนำไปใช้ตามลำดับซึ่งดูเหมือนจะเพียงพอที่จะหลีกเลี่ยงการชนกันของตัวเองในเกือบทุกกรณี

เมื่อตรวจพบการชนกันของตัวเองการโค้งที่นำไปสู่การกระทำผิดจะถูกนำไปใช้ในคำสั่งซื้อที่เป็นไปได้ทั้งหมดจนกว่าจะพบการพับที่ถูกต้องหรือการรวมทั้งหมดหมดลง

ตรวจสอบแบบคงที่

ก่อนที่จะคิดเกี่ยวกับชิ้นส่วนที่เคลื่อนไหวฉันพบว่ามันมีประสิทธิภาพมากขึ้นในการทดสอบรูปทรงสุดท้ายของงูสำหรับจุดตัดด้วยตนเอง

ทำได้โดยการวาดงูบนกริด จุดที่เป็นไปได้แต่ละจุดจะถูกพล็อตจากหัวลง หากมีจุดตัดด้วยตนเองอย่างน้อยคู่ของคะแนนจะตกอยู่ในตำแหน่งเดียวกัน สิ่งนี้ต้องการพล็อต N ที่แน่นอนสำหรับการกำหนดค่างูใด ๆ เป็นเวลา O (N) คงที่

ข้อได้เปรียบหลักของวิธีนี้คือการทดสอบแบบสแตติกเพียงอย่างเดียวจะเลือกเส้นทางการหลีกเลี่ยงตัวเองที่ถูกต้องบนตารางสี่เหลี่ยมซึ่งช่วยให้ทดสอบอัลกอริทึมทั้งหมดโดยยับยั้งการตรวจจับการชนแบบไดนามิกและทำให้แน่ใจว่าเราพบจำนวนเส้นทางที่ถูกต้อง

ตรวจสอบแบบไดนามิก

เมื่องูพับหนึ่งรอบรอยต่อแต่ละส่วนที่หมุนจะกวาดพื้นที่ที่มีรูปร่างเป็นอะไรนอกจากเล็กน้อย
เห็นได้ชัดว่าคุณสามารถตรวจสอบการชนกันโดยการทดสอบการรวมอยู่ในพื้นที่กวาดทั้งหมดเป็นรายบุคคล การตรวจสอบทั่วโลกจะมีประสิทธิภาพมากขึ้น แต่ให้ความซับซ้อนด้านที่ฉันไม่สามารถคิดได้ (ยกเว้นอาจใช้ GPU เพื่อวาดทุกพื้นที่และดำเนินการตรวจสอบระดับโลก)

เนื่องจากการทดสอบแบบสถิตจะดูแลตำแหน่งเริ่มต้นและจุดสิ้นสุดของแต่ละเซกเมนต์เราเพียงแค่ต้องตรวจสอบจุดตัดด้วยส่วนโค้งที่กวาดโดยแต่ละส่วนที่หมุน

หลังจากการสนทนาที่น่าสนใจกับ trichoplax และJavaScript เล็กน้อยเพื่อให้ได้แบริ่งของฉันฉันมาด้วยวิธีนี้:

หากต้องการลองใส่เป็นคำสองสามคำหากคุณโทรหา

  • Cศูนย์กลางของการหมุน
  • Sส่วนที่หมุนได้ของความยาวและทิศทางที่ไม่มีC ,
  • LสายยืดS
  • Hเส้นตั้งฉากกับLผ่านC ,
  • ผมตัดของLและH ,

เลข
(ที่มา: free.fr )

สำหรับเซ็กเมนต์ใด ๆ ที่ไม่มีIพื้นที่กวาดถูกผูกไว้ด้วย 2 ส่วนโค้ง (และ 2 เซ็กเมนต์ได้รับการตรวจสอบแบบคงที่แล้ว)

ถ้าฉันตกอยู่ในกลุ่มอาร์คจะถูกกวาดโดยฉันจะต้องนำมาพิจารณาด้วย

ซึ่งหมายความว่าเราสามารถตรวจสอบแต่ละส่วนที่ไม่ได้ทำการเคลื่อนย้ายเทียบกับแต่ละเซกเมนต์หมุนด้วย 2 หรือ 3 เซกเมนต์พร้อมกับส่วนโค้ง

ฉันใช้เรขาคณิตแบบเวกเตอร์เพื่อหลีกเลี่ยงฟังก์ชันตรีโกณมิติโดยสิ้นเชิง
การดำเนินการของ Vector จะสร้างรหัสที่กะทัดรัดและอ่านได้ค่อนข้างง่าย

การแยกเซกเมนต์กับอาร์คต้องใช้เวกเตอร์จุดลอยตัว แต่ตรรกะควรมีภูมิคุ้มกันต่อข้อผิดพลาดในการปัดเศษ
ฉันพบโซลูชันที่หรูหราและมีประสิทธิภาพนี้ในโพสต์ฟอรัมที่ไม่ชัดเจน ฉันสงสัยว่าทำไมมันไม่เผยแพร่อย่างกว้างขวางมากขึ้น

มันใช้งานได้หรือไม่

การยับยั้งการตรวจจับการชนกันแบบไดนามิกทำให้เส้นทางการหลีกเลี่ยงตัวเองที่ถูกต้องนับได้ถึง n = 19 ดังนั้นฉันค่อนข้างมั่นใจว่าโครงร่างทั่วโลกทำงานได้

การตรวจจับการชนกันแบบไดนามิกสร้างผลลัพธ์ที่สอดคล้องกันแม้ว่าการตรวจสอบการโค้งตามลำดับจะหายไป (ตอนนี้)
เป็นผลให้โปรแกรมนับงูที่สามารถโค้งงอจากหัวลง (เช่นกับข้อต่อพับตามลำดับเพื่อเพิ่มระยะห่างจากหัว)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.