救命,我陷入了Sierpinski三角!


44

绘制谢尔宾斯基三角形已经被 。我们还有其他有趣的事情可以做。如果我们在三角形处斜视的程度足够大,则可以将上下颠倒的三角形视为分形图的节点。让我们找到解决该图的方法!

首先,让我们为每个节点分配一个数字。最大的倒置三角形将是节点零,然后我们逐层向下移动(宽度优先),并按从上到下的顺序分配连续的数字:

在此处输入图片说明
单击以获取较大的版本,其中较小的数字不太模糊。

(当然,在该模式的蓝色三角形内部循环往复。)来定义的编号的另一种方式是,该中心节点具有索引0,并且节点的子节点i(下一个较小规模的相邻三角形)具有指数3i+13i+23i+3

我们如何围绕该图移动?任何给定的三角形最多可以采取六个自然步骤:

  • 一个人总是可以通过边之一的中点移动到当前节点的三个子节点之一。我们会指定这些举措为NSWSE。例如,如果我们现在走的节点2,这将导致节点789分别。不允许通过边缘进行其他移动(到达间接后代)。
  • 只要不碰到三角形的边缘,也可以通过三个角之一移至直接父级或两个间接祖先之一。我们会指定这些举措为SNENW。例如,如果我们当前在node上31S将导致10NE无效并NW导致0

挑战

给定两个非负整数xand y,仅使用上述六个动作找到从x到的最短路径y。如果有几个最短的路径,请输出其中任何一条。

请注意,您的代码不仅仅可以在上图中描述的5个级别中工作。您可能会认为x, y < 1743392200。这样可以确保它们适合32位带符号整数。请注意,这对应于树的20个级别。

您的代码必须在5秒钟内处理任何有效输入。尽管这排除了蛮力广度优先搜索,但它应该是一个相当宽松的约束-我的参考实现在半秒内处理了深度1000的任意输入(对于节点来说约为480位数字)。

您可以编写程序或函数,通过STDIN(或最接近的替代方案),命令行参数或函数自变量获取输入,并通过STDOUT(或最接近的替代方案),函数返回值或函数(out)参数输出结果。

输出应该是字符串的平面,明确的清单NSNENWSESW,使用任何合理的分隔符(空格,换行,逗号,","...)。

适用标准规则。

测试用例

可以使用上图手动计算出前几个测试用例。其他人确保答案足够有效。对于那些,可能没有列出其他相同长度的解决方案。

0 40                    => N N N N
66 67                   => S SW N N N
30 2                    => NW NW -or- NE SW
93 2                    => NE SW
120 61                  => NW NW NW NW N SE SW N
1493682877 0            => S S NW NW
0 368460408             => SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130 1242824      => NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
520174 1675046339       => NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
312602548 940907702     => NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873   => NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
547211529 1386725128    => S S S NE NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199   => NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

Answers:


5

红宝石,195个 194 190 184字节

原文:Ell表示歉意,因为这实质上是他们答案的一部分,并非常感谢Doorknob在调试此答案方面的帮助。这个问题可能还有另一种算法-与之相关*f[x[0,**however far x matches with y**],y]-但我会再次保存它。

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
f=->x,y{j=x.size
(Array===x)?x==y[0,j]?y[j..-1].map{|m|%w[SE SW N][m]}:x.uniq.map{|m|[%w[NW NE S][m],*f[x[0,x.rindex(m)],y]]}.min_by(&:size):f[a[x],a[y]]}

编辑: 贪婪算法不适用于h[299792458, 1000000]。我将字节数返回到195,同时再次修复算法。固定它只为字节数上升到203 叹息

正在建设中:该程序使用贪婪算法来查找共同祖先x[0,j]==y[0,j](注意:可能有几个共同祖先)。该算法非常宽松地基于Ell的递归祖先搜索。结果指令的前半部分是如何到达该共同祖先的,后半部分是基于到达y的y[j..-1]

注意:a[n]此处使用数字代替,返回基数为3的双射数2,1,01,2,3

例如,让我们来看一下f[59,17]f[[2,0,2,1],[2,1,1]]。在这里,j == 1。去x[0,j],我们去0NW。然后y,前往[1,1]SW SW

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
h=->m,n{x=a[m];y=a[n];c=[];j=x.size
(j=x.uniq.map{|m|k=x.rindex(m);x[0..k]==y[0..k]?j:k}.min
c<<%w[NW NE S][x[j]];x=x[0,j])until x==y[0,j]
c+y[j..-1].map{|m|%w[SE SW N][m]}}

45

Python 2 208 205 200字节

A=lambda n:n and A(~-n/3)+[-n%3]or[]
f=lambda x,y:f(A(x),A(y))if x<[]else["SSNEW"[m::3]for m in
y[len(x):]]if x==y[:len(x)]else min([["NNSWE"[m::3]]+f(x[:~x[::-1].index(m)],y)for
m in set(x)],key=len)

函数f带有一对节点号,并返回最短路径作为字符串列表。

说明

我们从对三角形采用不同的寻址方案开始;每个三角形的地址是一个字符串,定义如下:

  • 中心三角形的地址是空字符串。

  • 北,南,西,每个三角形的东南孩子的地址追加形成01以及2分别在三角形的地址。

本质上,每个三角形的地址编码从中心三角形到它的(最短)路径。我们的程序要做的第一件事是将输入的三角形数字转换为相应的地址。

图1

单击图像查看大图。

可以根据地址轻松确定每个三角形处的可能移动:

  • 要移动到北部,西南部和东南部的孩子,我们简单地追加012分别的地址。

  • 要移动到南部,东北部和西北部的祖先,我们找到的最后一个(最右边)的发生012分别,和微调地址到左边的吧。如果地址中没有012,则相应的祖先不存在。例如,要移至112(即其父级)的西北祖先,我们发现2in 的最后一次出现(即112最后一个字符),并在其左侧修剪地址,从而得到11; 要移至东北祖先,我们找到1in 的最后一个出现112,它是第二个字符,并在其左侧修整地址,从而给我们1;但是,112由于没有祖先,所以没有南祖先0 在其地址中。

需要注意的几件事情讲述了一对地址,x并且y

  • 如果x是的初始子串y,则y是的后代x,因此从x到的最短路径y简单地遵循x和之间的每个三角形的对应子代y。换句话说,我们可以相互替换01以及2y[len(x):]NSWSE分别。

  • 否则,i设为x和之间的第一个不匹配项的索引y。有没有从路径xy不通过x[:i](其是相同的y[:i]),即,所述第一共同祖先xy。因此,从x到的任何路径都y必须到达x[:i],或其祖先之一,我们称这个三角形z,然后继续y。若要从到货xz,我们遵循祖先如上所述。从z到的最短路径y由上一个项目符号点给出。

如果x是的初始子串y,则上面的第一个项目符号点很容易给出从x到的最短路径y。否则,我们让j是最小的最后出现的指标01以及2x。如果j大于或等于,之间的第一失配的索引xyi我们简单地添加相应的移动(SNE,或NW,分别地)的路径,修剪x到左的j,并继续。如果j小于i,事情会变得更加棘手,因为从那时起,我们可能会直接y上升到共同祖先x[:j]并一直下降到y或者我们也许能够去一个不同的共同祖先x,并y认为更接近于y由上升到一个不同的祖先x到右侧i,并从那里得到y更快。例如,要从1222到达1,最短的路径是首先上升到中心三角形(其地址是空字符串),然后下降到1,即,第一步将我们带到不匹配点的左侧。但是,要从1222到达12,最短的路径是先上升到122,然后再上升到12,即,第一个举动将我们带到错配点的右边。

那么,我们如何找到最短的路径?“官方”程序使用暴力破解方法,只要x不是的初始子串,就尝试所有可能的移动到任何祖先y。听起来还不错!它在一两秒钟之内就解决了所有测试用例。

但是话又说回来,我们可以做得更好:如果不匹配点的左边有多个直接可联系的祖先,我们只需要测试最右边的一个,而如果不匹配的祖先不止一个,在不匹配点的右边,我们只需要测试最左边的那个。这将产生一个线性时间算法,该算法的长度x(即源三角形的深度,或与源三角形数量的对数成正比的时间)可以放大更大的测试用例。以下程序至少在本质上实现了该算法-由于打高尔夫球,其复杂性实际上比线性复杂,但仍然非常快。

Python 2 271 266 261字节

def f(x,y):
 exec"g=f;f=[]\nwhile y:f=[-y%3]+f;y=~-y/3\ny=x;"*2;G=["SSNEW"[n::3]for
n in g];P=G+f;p=[];s=0
 while f[s:]:
    i=len(f)+~max(map(f[::-1].index,f[s:]));m=["NNSWE"[f[i]::3]]
    if f[:i]==g[:i]:P=min(p+m+G[i:],P,key=len);s=i+1
    else:p+=m;f=f[:i]
 return P

请注意,与较短的版本不同,此版本专门编写为在输入值到其对应地址的转换中不使用递归,以便它可以处理非常大的值而不会导致堆栈溢出。

结果

以下代码段可用于运行任一版本的测试并生成结果:

def test(x, y, length):
    path = f(x, y)
    print "%10d %10d  =>  %2d: %s" % (x, y, len(path), " ".join(path))
    assert len(path) == length

#         x           y        Length
test(          0,          40,    4   )
test(         66,          67,    5   )
test(         30,           2,    2   )
test(         93,           2,    2   )
test(        120,          61,    8   )
test( 1493682877,           0,    4   )
test(          0,   368460408,   18   )
test( 1371432130,     1242824,   17   )
test(     520174,  1675046339,   23   )
test(  312602548,   940907702,   19   )
test( 1238153746,  1371016873,   22   )
test(  547211529,  1386725128,   23   )
test( 1162261466,  1743392199,   38   )

高尔夫版

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NE SW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: S S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NE NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

高效版

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NW NW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: NE S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

6
该死的太快了。我无法告诉您,每当我让您回答我的挑战之一时,它让我多么高兴。:)
Martin Ender 2015年

2
@MartinBüttner谢谢,这是一个巨大的赞美!FWIW,我非常乐于解决您的挑战。我可能会或可能不会开始研究仍在沙盒中的这个工具……:)
Ell 2015年

2
该寻址方案非常出色。这太棒了。
BrainSteel

1
@BrainSteel对我而言,发生的第一件事就是尝试使用该寻址方案,但是在不到一个小时的时间内看到整个概念的实现,实现编写,真是太棒了。+1
里弗

1
@Zymus我不确定我是否遵循,但是如果您指的是图片,则不应该与OP匹配-这是一种不同的寻址方案,如该帖子所述。,
Ell 2015年

3

APL(Dyalog Unicode)的144 132 129 118 133 132 130 124 117 字节SBCS

非常感谢Ven和ngn在APL果园打高尔夫球的帮助,这是学习APL语言的好地方。⎕IO←0。欢迎打高尔夫球。

编辑:通过更改n定义方式并从1索引切换到0索引,感谢Ven和ngn -12字节。-3由于修复了并非将所有内容都切换为0索引的错误。由于更改了PQ定义了-11个字节。+15个字节,这是由于解决了我的算法不正确的问题,这要多亏了ngn协助我们确定了该s[⊃⍋|M-s]部分。-2个字节(从重新排列查找回溯路径的方法和+1个字节)到错误修复。-2个字节,感谢Adám重新安排了的定义I。-6个字节,这要感谢ngn重新安排了定义'S' 'NE' 'NW' 'N' 'SW' 'SE'以及如何重新t定义了(不再是一个单独的变量)。-7个字节归功于golf的ngn如何s定义。

{M←⊃⍸≠⌿↑1+P Q←⍵{(⍵/3)⊤⍺-+/3*⍳⍵}¨⌊31+⍵×2⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵)s[⊃⍋|M-s←⌽⊢.⊢⌸⍵]}P]}

在线尝试!

算法错误的解释

基本的问题是,我认为最短的路径直接通过了共同祖先,而实际上却无法通过共同祖先。这是不正确的,如以下示例所示。

从66到5

66  0 2 2 2  0 2 2 2
5   0 1      0 1
       common ancestor

The two ancestors of 0 2 2 2 are:
0 2 2
(empty)

(empty) has the shorter path back to 0 1 as it only needs two forward moves,
while 0 2 2 requires two more backtracks and one more forward move.

从299792458至45687

299792458  0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2 0
45687      0 2 1 1 0 1 1 1 2 2
                          common ancestor

The three ancestors of 299792458 are:
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2
0 2 1 1 0 1 1 2 1 1 1 2             choose this one
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2

And the three ancestors of 0 2 1 1 0 1 1 2 1 1 1 2 are:
0 2 1 1
0 2 1 1 0 1 1 2 1 1
0 2 1 1 0 1 1 2 1 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

While it seems like `0 2 1 1` is the shorter path,
it actually results in a path that is 8 steps long
(2 backtracks, 6 forward steps to 45687).

Meanwhile, `0 2 1 1 0 1 1 2 1 1` is at an equal distance
to the common ancestor and has the following ancestors:
0 2 1 1
0 2 1 1 0 1 1 2 1
0 2 1 1 0 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

Clearly, this is the superior path, as with three backtracks, we have reached
the point of the common ancestor. With 3 backtracks and 3 forward moves,
we have a path that is 6 steps long.

代码说明

                         should be an array of 2 integers, x y
SierpinskiPath←{⎕IO0   0-indexing
         P Q←{...}¨⍵   First, the bijective base-3 numeration of x and y
    P Q←{
        n←⌊31+⍵×2   The number of digits in the numeration
        z←+/3*⍳⍵     The number of numerations with  n digits
        (n/3)⊤⍵-z    And a simple decode  (base conversion) of ⍵-z
    }¨⍵              gets us our numerations, our paths

    A←↑1+P Q       We turn (1+P Q) into an 2-by-longest-path-length array 
                    pads with 0s and our alphabet also uses 0s
                   So we add 1 first to avoid erroneous common ancestor matches
    Common←⊃⍸≠⌿A   We find the length of the common ancestor, Common

         I←⍬{...}P   Now we get the shortest backtracking path from P
    I←⍬{
        Common=≢⍵:⍺        If P is shorter than Common, return our backtrack path
        s←⌽⊢.⊢⌸⍵           Get the indices of the most recent N SW SE
        ts[⊃⍋|Common-s]   and keep the index that is closest to Common
                           and favoring the ancestors to the right of
                           Common in a tiebreaker (which is why we reverse ⊢.⊢⌸⍵)
        (⍺,t)∇t↑⍵          Then repeat this with each new index to backtrack
    }P                     and the path left to backtrack through

    BacktrackP[I]    We get our backtrack directions
    Forward←(⊃⌽I)↓Q   and our forward moves to Q
                      starting from the appropriate ancestor
    (∪¨↓6 2'SSNENWNNSWSE')[Backtrack,Forward]     'S' 'NE' 'NW' 'N' 'SW' 'SE'
}                     and return those directions

使用Dyalog Extended和dfns的替代解决方案

如果我们使用⎕CY 'dfns'adic函数,它将以更少的字节数实现双射基数n(这是我使用的版本的灵感)。切换到Dyalog Extended也可以节省很多字节,因此就在这里。非常感谢Adám在打高尔夫球方面的帮助。欢迎打高尔夫球!

编辑: -8字节由于更改方式PQ定义。-14字节(由于切换到Dyalog扩展)。-2由于使用完整程序删除dfn括号{}。+17个字节,这是由于解决了我的算法不正确的问题所致,这要多亏了ngn提供的帮助来确定该s[⊃⍋|M-s]部分。+1个字节以修复错误。多亏了Adám重新安排了-2个字节的定义,I 并记住了将我的高尔夫球放入这两种解决方案中,所以有了-1个字节。通过重新排列基数方向的生成,使-3字节归因于ngn,通过修正高尔夫球车使+1字节归因于错误,通过重新t定义定义方式,对-3字节归因于ngn (不再是单独的变量)。通过重新排列s定义方式,感谢ngn -7个字节。

APL(Dyalog扩展)123 115 101 99 116个 117 114 109 102字节

M←⊃⍸≠⌿↑1+P Q←(⍳3)∘⌂adic¨⎕⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵){⍵[⊃⍋|M-⍵]}⌽⊢.⊢⌸⍵}P]

在线尝试!


对于66和1,这不通过0找到最短路径
基督教西弗斯

@ChristianSievers您绝对正确,但我不确定如何解决此问题。谢谢你让我知道。
Sherlock
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.