Python 3-临时评分:n = 11(使用PyPy *时,n = 13)
由于第一周没有答案,因此以下是Python中鼓励竞争的示例。我已尝试使其具有合理的可读性,以便可以轻松地识别效率低下的问题,并为其他答案提供思路。
方法
- 从笔直的蛇开始,找到所有可以一举获得的合法位置。
- 从尚未确定的职位中查找所有可以合法到达的职位。
- 重复直到找不到更多的位置,然后返回找到的位置总数。
码
(现在有了一些doctest,并在我不正确的第一次尝试后断言)
'''
Snake combinations
A snake is represented by a tuple giving the relative orientation at each joint.
A length n snake has n-1 joints.
Each relative orientation is one of the following:
0: Clockwise 90 degrees
1: Straight
2: Anticlockwise 90 degrees
So a straight snake of length 4 has 3 joints all set to 1:
(1, 1, 1)
x increases to the right
y increases upwards
'''
import turtle
def all_coords(state):
'''Return list of coords starting from (0,0) heading right.'''
current = (1, 0)
heading = 0
coords = [(0,0), (1,0)]
for item in state:
heading += item + 3
heading %= 4
offset = ((1,0), (0,1), (-1,0), (0,-1))[heading]
current = tuple(current[i]+offset[i] for i in (0,1))
coords.append(current)
return coords
def line_segments(coords, pivot):
'''Return list of line segments joining consecutive coords up to pivot-1.'''
return [(coords[i], coords[i+1]) for i in range(pivot+1)]
def rotation_direction(coords, pivot, coords_in_final_after_pivot):
'''Return -1 if turning clockwise, 1 if turning anticlockwise.'''
pivot_coord = coords[pivot + 1]
initial_coord = coords[pivot + 2]
final_coord = coords_in_final_after_pivot[0]
initial_direction = tuple(initial_coord[i] - pivot_coord[i] for i in (0,1))
final_direction = tuple(final_coord[i] - pivot_coord[i] for i in (0,1))
return (initial_direction[0] * final_direction[1] -
initial_direction[1] * final_direction[0]
)
def intersects(arc, line):
'''Return True if the arc intersects the line segment.'''
if line_segment_cuts_circle(arc, line):
cut_points = points_cutting_circle(arc, line)
if cut_points and cut_point_is_on_arc(arc, cut_points):
return True
def line_segment_cuts_circle(arc, line):
'''Return True if the line endpoints are not both inside or outside.'''
centre, point, direction = arc
start, finish = line
point_distance_squared = distance_squared(centre, point)
start_distance_squared = distance_squared(centre, start)
finish_distance_squared = distance_squared(centre, finish)
start_sign = start_distance_squared - point_distance_squared
finish_sign = finish_distance_squared - point_distance_squared
if start_sign * finish_sign <= 0:
return True
def distance_squared(centre, point):
'''Return the square of the distance between centre and point.'''
return sum((point[i] - centre[i]) ** 2 for i in (0,1))
def cut_point_is_on_arc(arc, cut_points):
'''Return True if any intersection point with circle is on arc.'''
centre, arc_start, direction = arc
relative_start = tuple(arc_start[i] - centre[i] for i in (0,1))
relative_midpoint = ((relative_start[0] - direction*relative_start[1])/2,
(relative_start[1] + direction*relative_start[0])/2
)
span_squared = distance_squared(relative_start, relative_midpoint)
for cut_point in cut_points:
relative_cut_point = tuple(cut_point[i] - centre[i] for i in (0,1))
spacing_squared = distance_squared(relative_cut_point,
relative_midpoint
)
if spacing_squared <= span_squared:
return True
def points_cutting_circle(arc, line):
'''Return list of points where line segment cuts circle.'''
points = []
start, finish = line
centre, arc_start, direction = arc
radius_squared = distance_squared(centre, arc_start)
length_squared = distance_squared(start, finish)
relative_start = tuple(start[i] - centre[i] for i in (0,1))
relative_finish = tuple(finish[i] - centre[i] for i in (0,1))
relative_midpoint = tuple((relative_start[i] +
relative_finish[i]
)*0.5 for i in (0,1))
determinant = (relative_start[0]*relative_finish[1] -
relative_finish[0]*relative_start[1]
)
determinant_squared = determinant ** 2
discriminant = radius_squared * length_squared - determinant_squared
offset = tuple(finish[i] - start[i] for i in (0,1))
sgn = (1, -1)[offset[1] < 0]
root_discriminant = discriminant ** 0.5
one_over_length_squared = 1 / length_squared
for sign in (-1, 1):
x = (determinant * offset[1] +
sign * sgn * offset[0] * root_discriminant
) * one_over_length_squared
y = (-determinant * offset[0] +
sign * abs(offset[1]) * root_discriminant
) * one_over_length_squared
check = distance_squared(relative_midpoint, (x,y))
if check <= length_squared * 0.25:
points.append((centre[0] + x, centre[1] + y))
return points
def potential_neighbours(candidate):
'''Return list of states one turn away from candidate.'''
states = []
for i in range(len(candidate)):
for orientation in range(3):
if abs(candidate[i] - orientation) == 1:
state = list(candidate)
state[i] = orientation
states.append(tuple(state))
return states
def reachable(initial, final):
'''
Return True if final state can be reached in one legal move.
>>> reachable((1,0,0), (0,0,0))
False
>>> reachable((0,1,0), (0,0,0))
False
>>> reachable((0,0,1), (0,0,0))
False
>>> reachable((1,2,2), (2,2,2))
False
>>> reachable((2,1,2), (2,2,2))
False
>>> reachable((2,2,1), (2,2,2))
False
>>> reachable((1,2,1,2,1,1,2,2,1), (1,2,1,2,1,1,2,1,1))
False
'''
pivot = -1
for i in range(len(initial)):
if initial[i] != final[i]:
pivot = i
break
assert pivot > -1, '''
No pivot between {} and {}'''.format(initial, final)
assert initial[pivot + 1:] == final[pivot + 1:], '''
More than one pivot between {} and {}'''.format(initial, final)
coords_in_initial = all_coords(initial)
coords_in_final_after_pivot = all_coords(final)[pivot+2:]
coords_in_initial_after_pivot = coords_in_initial[pivot+2:]
line_segments_up_to_pivot = line_segments(coords_in_initial, pivot)
direction = rotation_direction(coords_in_initial,
pivot,
coords_in_final_after_pivot
)
pivot_point = coords_in_initial[pivot + 1]
for point in coords_in_initial_after_pivot:
arc = (pivot_point, point, direction)
if any(intersects(arc, line) for line in line_segments_up_to_pivot):
return False
return True
def display(snake):
'''Display a line diagram of the snake.
Accepts a snake as either a tuple:
(1, 1, 2, 0)
or a string:
"1120"
'''
snake = tuple(int(s) for s in snake)
coords = all_coords(snake)
turtle.clearscreen()
t = turtle.Turtle()
t.hideturtle()
s = t.screen
s.tracer(0)
width, height = s.window_width(), s.window_height()
x_min = min(coord[0] for coord in coords)
x_max = max(coord[0] for coord in coords)
y_min = min(coord[1] for coord in coords)
y_max = max(coord[1] for coord in coords)
unit_length = min(width // (x_max - x_min + 1),
height // (y_max - y_min + 1)
)
origin_x = -(x_min + x_max) * unit_length // 2
origin_y = -(y_min + y_max) * unit_length // 2
pen_width = max(1, unit_length // 20)
t.pensize(pen_width)
dot_size = max(4, pen_width * 3)
t.penup()
t.setpos(origin_x, origin_y)
t.pendown()
t.forward(unit_length)
for joint in snake:
t.dot(dot_size)
t.left((joint - 1) * 90)
t.forward(unit_length)
s.update()
def neighbours(origin, excluded=()):
'''Return list of states reachable in one step.'''
states = []
for candidate in potential_neighbours(origin):
if candidate not in excluded and reachable(origin, candidate):
states.append(candidate)
return states
def mirrored_or_backwards(candidates):
'''Return set of states that are equivalent to a state in candidates.'''
states = set()
for candidate in candidates:
mirrored = tuple(2 - joint for joint in candidate)
backwards = candidate[::-1]
mirrored_backwards = mirrored[::-1]
states |= set((mirrored, backwards, mirrored_backwards))
return states
def possible_snakes(snake):
'''
Return the set of possible arrangements of the given snake.
>>> possible_snakes((1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1))
{(1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1)}
'''
reached = set()
candidates = set((snake,))
while candidates:
candidate = candidates.pop()
reached.add(candidate)
new_candidates = neighbours(candidate, reached)
reached |= mirrored_or_backwards(new_candidates)
set_of_new_candidates = set(new_candidates)
reached |= set_of_new_candidates
candidates |= set_of_new_candidates
excluded = set()
final_answers = set()
while reached:
candidate = reached.pop()
if candidate not in excluded:
final_answers.add(candidate)
excluded |= mirrored_or_backwards([candidate])
return final_answers
def straight_derived_snakes(length):
'''Return the set of possible arrangements of a snake of this length.'''
straight_line = (1,) * max(length-1, 0)
return possible_snakes(straight_line)
if __name__ == '__main__':
import doctest
doctest.testmod()
import sys
arguments = sys.argv[1:]
if arguments:
length = int(arguments[0])
else:
length = int(input('Enter the length of the snake:'))
print(len(straight_derived_snakes(length)))
结果
在我的机器上,可以在1分钟内计算出的最长的蛇是长度11(或PyPy *为13)。显然,这只是一个临时评分,直到我们从Lembik的机器中找出官方评分是多少。
为了进行比较,以下是n的前几个值的结果:
n | reachable orientations
-----------------------------
0 | 1
1 | 1
2 | 2
3 | 4
4 | 9
5 | 22
6 | 56
7 | 147
8 | 388
9 | 1047
10 | 2806
11 | 7600
12 | 20437
13 | 55313
14 | 148752
15 | 401629
16 | 1078746
17 | MemoryError (on my machine)
如果有任何不正确的地方,请告诉我。
如果您有一个不应该展开的排列示例,则可以使用该功能neighbours(snake)
一步找到所有可排列的排列,作为对代码的测试。snake
是关节方向的元组(0表示顺时针,1表示笔直,2表示逆时针)。例如(1,1,1)是长度为4(带有3个关节)的直蛇。
可视化
要可视化您所想到的蛇或返回的任何蛇neighbours
,可以使用函数display(snake)
。与其他函数一样,它接受一个元组,但是由于主程序不使用它,因此不需要快速,因此为了方便起见,它还将接受一个字符串。
display((1,1,2,0))
相当于 display("1120")
正如Lembik在评论中提到的那样,我的结果与OEIS A037245相同,后者没有考虑旋转过程中的相交。这是因为对于n的前几个值没有区别-可以通过折叠一条直线蛇来获得所有不自相交的形状。可以通过调用neighbours()
没有交集就无法展开的蛇来测试代码的正确性。由于它没有邻居,它将仅对OEIS序列起作用,而对这一序列不起作用。我知道的最小例子是Lembik提到的这条长31条蛇,这要感谢David K:
(1,1,1,1,2,1,2,1,1,1,1,1,1,2,1,1,1,2,1,1,2,2,1,0,1,0,1,1,1,1)
display('111121211111121112112210101111')
给出以下输出:
提示:如果您调整显示窗口的大小,然后再次调用显示,则蛇将适合新的窗口大小。
我很想听听任何一个没有邻居的简短例子的人的来信。我怀疑最短的此类示例将标记两个序列不同的最小n。
*请注意,n的每个增量大约要花费3倍的时间,因此从n = 11增加到n = 13需要几乎10倍的时间。这就是为什么PyPy比标准Python解释器运行得快得多的原因,只允许将n增加2。