Office Escape:计划出路!


32

这是最后的冲刺……您的团队有一半生病了。您工作到很晚,只是做最后一天的工作,期待...为什么灯会熄灭?我不记得保安人员来了...哦,不!我把钥匙留在家中!

随着局势的恐惧逐渐加深,您决定要逃脱

任务摘要

要实现逃生,您需要一个计划!但是,您知道任何计划都有失败的可能,并且不同的计划需要不同的努力。

由于感到饥饿,疲倦并且是一名工程师,您决定编写一个简短的程序来确定逃避复杂事件的最佳方法,从而平衡对成功可能性及其所需工作量的担忧。

您绘制建筑物的地图:

#######################
#                =    #
!                =    !    <-- window
#          !     =    #        (freedom!)
#################=    #
#    #           =    #
#    #           =    #
#    #           =    #
# o  !   # #  !  =    #
##|  !  ## #  !  =    #
#######################

  ^  ^           ^
  me my door     ludicrously high shelves
     (locked)    (climbable)

为了逃脱办公室,您将不得不离开地图。在这里,您会看到2个窗口(!),其中任何一个窗口都会带您进入自由,但是只有其中一个窗口可以访问。我们将“不在地图上”定义为您的脚不在地图范围内

细胞类型

  - empty, you can be here (i.e. the escapee can consume this cell)
# - solid (your desk, your in-tray), you can't be here, but you can stand on it
! - destructible, (co-worker's desk, door, window), you can't be here until you smash it first (turning it into an empty cell)
= - climbable, (fire ladder, filing cabinet, etc.), you can be here

逃生者最初消耗的细胞被认为是空的。

动作规格

您的一次性设备有多种可能的动作。这些是由具有某些整数成功概率的简单状态转换定义的。例如,对于步行,您将逃逸者移动一个单元格,我们通过此过渡表示该单元格:

1 stp,100%,镜子

 o.            o
 |.   -->      |
 #            #

点表示由于我们进入其中或经过它而必须为空的单元格(或可爬升的,但不是实心或可破坏的)。100%意味着您有100%的机会不伤害自己并结束大胆的逃脱。所有概率都是介于1%和100%(含)之间的整数百分比。第一张图显示了初始状态(站在实体上,站在一些空白处)。第二张图显示了终端状态(移入空白区域)。左侧(初始状态)的任何未指定的单元格(空格)没有特别要求。未指定的单元格(空格,)(右侧)(末尾状态)应与之前相同(例如,逃生者身后的任何东西,或者我碰巧走到的任何东西(无论是空白处还是其他地方)。请注意,右侧(末尾状态) ),图表只会将单元格从可破坏的状态更改为空的状态,不会发生其他变化。“ 1 stp”表示需要花费1 stp:我们将“ stp”定义为采取步骤所需的能量。

“镜像”表示此动作有两种形式。显示了“右”动作,“左”动作是一个精确的镜像,例如:

.o
.|
 # 

是镜子(左)的形式

o.
|.
# 

右边的动作称为“向右”(例如,“向右踩”)左边的动作称为“向左”(例如,“向左踩”)

在这些图中,逃生者显示为

o
|

站立时(2个单位高),并且

%

蹲伏时(1个单位高)。必须为实体或可破坏的单元格用井号表示#。不能为实体或可破坏的单元格用点表示.。爆炸表示必须破坏的细胞!。下划线显示新创建的空白空间_x是不会移动的参考点(它不存在,对该单元格必须是什么没有约束(如space ))。

注意:当您撞到地板时,我们会忽略快速减速的问题,是的,在此游戏中,您可以完全史诗般地跳上梯子)

1 stp,100%,镜子

 o.         o
 |.  -->    |
x#        x#

爬上去

1 stp,100%,镜子

 =         =
 o.  -->    o
 |.         |
x=        x= 

随机播放

3 stp,100%,镜面

 %.         %
x#   -->  x# 

爬起来

10 stp,95%,镜子

 o.         %
 |#  -->    #
x#        x#

下降

0 stp,100%

 o         
 |  -->   o
x.       x|

掉落(站立)

0 stp,100%

 %        o
x.  -->  x|

爬上

2 stp,100%

 =        o
 o  -->   |
x|       x

蹲伏

2 stp,100%

 o
 |  -->   %
x#       x#

支架

4 stp,100%

 .        o
 %  -->   |
x#       x#

短跳

4 stp,95%,镜子

 o..          o
 |..  -->     |
x#         x#

跳远

7 stp,75%,镜子

 o...           o
 |...  -->      |
x#          x#

跳高

12 stp,90%,镜子

 ..         o
 o.  -->    |
 |          
x#        x# 

放回去吧!

20 stp,80%,镜子

 o!.         _o
 |!.  -->    _|
x#         x#

冲床

8 stp,90%,镜子

 o!        o_
 |   -->   |
x#        x#

8 stp,85%,镜子

 o         o
 |!  -->   |_
x#        x#

邮票

8 stp,90%

 o        o
 |  -->   |
x!       x_

计划

计划是上面定义的一系列动作。例如:

Step Left
High Jump Left
Crouch
Shuffle Left
Shuffle Left
Stand
Long Jump Left
Put your back into it! Left
Step Left

请注意包含滴。应该设置规则,以阻止您执行除丢弃操作之外的任何操作,但是您仍然必须为此计划!

任何计划都有必要的工作量,这是每个步骤的工作量之和。还有一个成功概率,它是每个动作的成功概率的乘积。简单的例子:

Step Right:          1stp,  100%
Long Jump Right:     7stp,  75%
Step Right:          1stp,  100%
Stamp:               8stp,  90%
Drop:                0stp,  100%
Drop:                0stp,  100%
Drop:                0stp,  100%
Drop:                0stp,  100%
Step Left:           1stp,  100%
Step Left:           1stp,  100%
High Jump Left:      12stp, 90%

Effort = 1+7+1+8+1+1+12 = 31
Success Probability = 75%*90*90% = 60.75%

对于地图在页面顶部的“工作实例”,可以发现一个要点

输入值

输入分为两部分,整数和字符数组或字符串。

整数是您的最小可接受成功率(百分比)。它将被解释为百分比,因此80表示您的计划必须以不小于80%的概率成功。

有效的地图是一个矩形,其中包括直立的转义符(最小尺寸为1x2),并且没有未指定的符号。所有行的长度将相同。您可以将输入作为一维定界字符串(定界符必须是单个一致字符,或者是CRLF和LFCR对之一),作为相似的一维数组或二维数组。如果您选择的输入格式没有以某种方式指示地图的宽度或高度,则您可以接受其他参数(必须在答案中明确说明)。如果适合,可以混合使用命令行参数和标准输入(例如,来自stdin的映射,来自argv的最小成功概率)。以下是有效和无效映射的示例。

有效:

o
|

有效:

  #     #
  !   o #
  !   | #
#########

无效(无转义符):

  #     #
  !     #
  !     #
#########

无效(转义符始终开始站立):

  #     #
  !     #
  !   % #
#########

无效(符号无效):

  #     #
  !  ~  #
  !     #
#########

无效(不是矩形/不同长度的行):

  #     #
  !   o #
  !   | # 
#########

您可能会假设您的输入有效(我不在乎您的程序如果处理了无效的输入,会做什么)。

输出量

输出为文本(ASCII)。可以作为字符串返回,或打印到标准输出。该计划必须以LF,CRLF或LFCR界定。同样,在所需的工作之后,必须还有另一个LF,CRLF或LFCR。尾随换行符是可选的。

您必须输出最佳计划以及所需的工作量,或者说“没有希望!” 如果不存在这样的计划。您无需输出成功的可能性。此文本可能带有也可能没有尾随换行符。

最佳计划被定义为一种计划(请参见上文),它需要最少的努力并至少具有给定的成功概率。请注意,您的概率计算必须准确,您可能无法假设浮点数不够好(这就是为什么我不希望您输出它们)。我将尝试设计测试用例以公平地测试它(如果您通过它们并且不做任何愚蠢的假设,那么您可能会认为您提交的内容有效)。

格式:

Required Effort: <effort>
<plan>

例如,对于输入

50
  #     #
  !   o #
  !   | #
#########

适当的输出为:

Required Effort: 23
Step Left
Step Left
Step Left
Kick Left
Punch Left
Step Left
Step Left
Step Left
Step Left

此处成功的概率为76.5%。

对于同一张地图,但成功概率最小为80%,您将不得不“投入”,这将需要更多的精力,但要满足成功概率标准。如果成功的最小概率大于80%,则您需要加倍努力(无论是猛击还是踢过门的一部分然后将其洗掉)。如果成功的最小概率为100%,则必须打印出“没有希望!”

例子

一个输入可能有多个有效计划,您的输出不一定要是完全正确的计划,但它必须具有正确的要求工作量并且是有效计划。您可以使用验证程序来检查解决方案(请参见下文)

输入:

100
o
|

输出:

Required Effort: 0
Drop

输入:

5
# =      #
# =      !
# = !  ! !
# =#######
# =      #
# =   o  #
# = ! |  #
##########

输出:

Required Effort: 57
Step Left
Kick Left
Step Left
Step Left
Step Left
Climb Up
Climb Up
Climb Up
Climb Up
Climb off Right
High Jump Right
Long Jump Right
Step Right
Drop
Kick Right
Crouch
Shuffle Right
Shuffle Right

输入:

60
#########
# ! #   #
! ! ! o #
! # ! | #
#########

输出:

Required Effort: 58
Step Left
Kick Left
Crouch
Shuffle Left
Shuffle Left
Stand
Punch Left
Clamber Up Left
Shuffle Left
Drop (Stand)
Kick Left
Crouch
Shuffle Left
Shuffle Left

对于相同的地图(但为80%),输出应为:

There is no hope!

对于相同的地图(但为50%),使用不同的计划时所需的工作量变为56)

输入:

50
#######################
#          #     =    #
!          #     =    !
#          #     =    #
######  #####!## =### #
#=   ##       #  =    #
#=#############  =    #
#=               =    #
#= o             =    #
#!!|             =    #
#######################

输出:

Required Effort: 121
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Step Right
Climb Up
Climb Up
Climb Up
Climb Up
Climb Up
Climb Up
Climb off Right
Long Jump Left
Step Left
Step Left
Stamp
Drop
Drop
Crouch
Shuffle Left
Shuffle Left
Shuffle Left
Shuffle Left
Shuffle Left
Shuffle Left
Stand
Clamber Up Left
Stand
Clamber Up Left
Stand
Step Left
Step Left
Step Left
Step Left
Punch Left
Clamber Up Left
Shuffle Left

输入:

66
######
#  ###
#o ! !
#| ! !
### ##
######

输出:

Required Effort: 37
Step Right
Put your back into it! Right
Kick Right
Crouch
Shuffle Right
Shuffle Right

输入:

此设计旨在检查可能会成为受害者的许多错误假设,因此可能看起来有些奇怪

30
###################
# ## # # #   #  = #
! ## #   #   #  = #
#      #   #    = #
##  ############= #
# ## #         #= #
# =  #         #= #
! =  #         #= #
# =  #         #= #
#o=  !          ==#
#|=  !           =#
#!= # ==########==#
#   #    !   !!  =#
#   #    !!  !  = #
#   # !!!!#########
#   # #           #
#   # #           #
###################

具有成功机会约束30的输出:

Required Effort: 199
Long Jump Right
Put your back into it! Right
<snip>

具有成功机会约束32的输出:

Required Effort: 200
Long Jump Right
Punch Right
<snip>

使用顶部的地图作为输入,成功概率限制为1%,您应该获得116的必需工作量(成功机会〜32%,这在我的测试程序中运行大约需要20秒)。

胜利标准

这是代码高尔夫球,请以最短的代码为准。

要获得资格,您的功能或程序必须能够运行,并且能够在一台合理的机器上在30分钟内解决上述每个测试用例。我的求解器在30秒内完成了每个任务。如果测试脚本(如下所示)在30分钟内运行,那么您肯定很高兴。

求解器,验证器,测试脚本和测试案例的示例(包含解决方案)

求解器和求解器验证程序的C#代码在此处可以作为要点获得。要点还包含file.txt,这是上述操作的机器可读(足够)的形式,并且是程序运行所必需的。该文件与规范之间的任何差异都不是故意的。

要点还包含许多测试用例(包括上面的所有示例),以及一个PowerShell脚本,用于自动针对它们运行提交。如果脚本告诉您特定测试失败,则可以运行OfficeEscapeSolver.exe testcase<n>.txt outputFromYourProgram.txt以查看更多详细信息。这些测试用例的示例解决方案在另一个Gist中

所有代码都是一团糟(尽管没有用),但是您不必为了static void Main(...)改变输出量而走得太远(随意使用此信息,我为您带来了好处!)。

通过测试用例并不一定意味着您的输出格式正确,因为脚本和验证程序非常慷慨。您的输出必须符合上述规范,才能使提交的内容有效。

如果您认为发现了求解程序或测试脚本的错误file.txt,或的测试案例中存在错误,请对此文章发表评论,或者通过SE Chat ping me。我可能不会注意到其他任何交流尝试。

我不提供Bash或Batch的测试脚本,因为我不知道它们,但是我很高兴提供翻译,如果人们想要的话,我会写一个C#版本。

邮政安布尔

有问题吗?不要拖延,今天问他们!

这项任务意味着需要付出努力,才能给认真的高尔夫球手一些东西以供其咬牙切齿。

感谢ais523对输入/输出的反馈。

如果人们想要更多(不想再发布此帖子),或者想要提供一些自己的东西,我可以在gist文件中提供更多的测试用例。


2
巨大的挑战!有时间我一定会试一试。:)
R. Kap's

您知道,跌落的危险(或更确切地说是底部的快速减速)可以通过将落差过渡的概率设为95%左右来建模。;)很好的挑战!
Martin Ender

天窗或隧道能逃脱吗?即领域的顶部和底部?还是只有左或右?
Moogie's

@Moogie确实可以!只要您的脚是自由的,您就可以自由(例如,请参见1x2测试用例,其中的解决方案只是下降一次)。我将添加一个小的测试用例,以测试从天花板上下来。
VisualMelon

3
向问题添加了赏金。这是一个值得回答的好问题。
程序员

Answers:


3

Perl 5,1425 1464 1481 1469 1485 1438字节

那是一个有趣的挑战!而且,令人震惊的是,看来我到目前为止的代码最短,不到1.5kB!:)

可以肯定的是我终于成功了。所使用的定界符为:,并且在顶部两行和另一侧的每一列都有额外的填充。因此,您输入以下内容:

60
#########
# ! #   #
! ! ! o #
! # ! | #
#########

我的程序将采用:(注意:结尾必须有一个冒号,但不能在开头!)

60
#########:# ! #   #:! ! ! o #:! # ! | #:#########:

我使用正则表达式将一张地图翻译为另一张地图,并通过蛮力解决。但是,如果已经以较少的努力和更大或相等的概率到达该地图,则不会将地图添加到列表中。一旦从该点开始所有可能的移动都被用尽,则将地图从列表中删除。该程序与显示我已经到达侧面或底部的正则表达式匹配并以“没有希望!”结束时成功结束。当地图列表用尽时。

不幸的是,为了舍弃几个字节而牺牲了很多效率,因此高尔夫球版本相当慢;)Perl尤其觉得评估很痛苦。

没有进一步的理由,

chop($P=<>,$_=<>);s/:/ : /g;$w++until/:.{$w}:/;$z=$"x$w;$_="(.{".$w--."})"for($c,$h,$i,$j);$_="$z:$z: $_$z";$n="[ =]";$d="([!#])";@s=split/,/,'Drop,Drop (Stand),Stepx,Climb offx,Shufflex,Clamber Upx,Put your back into it!x,Punchx,Kickx,Short Jumpx,Long Jumpx,High Jumpx,Crouch,Stand,Climb Up,Stamp';@c=split/,/,"o$c\\|$c$n/ \$1o\$2|,%$c$n/o\$1|,o$n$h\\|$n$h${d}/ o\$1 |\$2\$3,=${c}o$n$h\\|$n$h=/=\$1=o\$2=|\$3=,%$n$h$d/ %\$1\$2,o$n$h\\|${d}$h${d}/ %".'$1 $2$3$4,'."o!$n$i\\|!$n$i${d}/  o\$1  |\$2\$3,o!$h\\|$c$d/o \$1|\$2\$3,\\|!$h$d/| \$1\$2,o$n$n$i\\|$n$n$i${d}/  o\$1  |\$2\$3,o$n$n$n$j\\|$n$n$n$j${d}/   o\$1   |\$2\$3,$n$n${h}o$n$h\\|$c$d/ o".'$1 |$2 $3$4,'."o$c\\|$c${d}/ \$1%\$2\$3,$n$c%$c${d}/o\$1|\$2\$3,=${c}o$c\\|/o\$1|\$2=,\\|$c!/|\$1 ";eval"*$_=sub{\$Q=\"$s[$_]\";s/$c[$_]/}"for(0..15);sub M{$_=join":",map{join'',reverse split//}split/:/};push@M,[$_,0,100,$P];$B{$_}=100;@E=(0,0,1,1,3,10,20,8,8,4,7,12,2,4,2,8);@P=map{100-$_}(0,0,0,0,0,5,20,10,15,5,25,10,0,0,0,10);$z='$N=[$_,$G+0,$t,$t[3]*100,"$t[4]$Q\n"];$N->[4]=~s/x/';do{sub A{@N=@$N;if($N[2]/$N[3]>$B{$N[0]}){push@M,$N;$B{$N[0]}=$N[2]/$N[3]}};die"There is no hope!\n"if(!(@M=grep$G-$_->[1]<21,@M));$e=-1;while(++$e<@M){@t=@{$M[$e]};$m=$_=$t[0];die"Required Effort: $t[1]\n$t[4]"if(/([|%]:|:[|%])/||/[|%][^:]*$/||/^$c:[^:]*[%|]/);for$x(0..15){$_=$m;$t=$t[2]*$P[$x];if($G==$E[$x]+$t[1]and$t>=$t[3]*100){&$x;eval"$z Right/";A;$_=$m;M;&$x;M;eval"$z Left/";A;}}}}

为了理智起见,以下是换行符和一些注释的相同之处:

chop($P=<>,$_=<>); #Take input
s/:/ : /g; #Pad columns on either side so escapee can leave that way
$w++until/:.{$w}:/; #Find width
$z=$"x$w;#Setup a blank line for use in padding later
$_="(.{".$w--."})"for($c,$h,$i,$j); #Set up some generic capturing regexes for reuse
$_="$z:$z: $_$z"; #Pad the top and bottom so the escapee can leave those ways
$n="[ =]"; #Regex for nonsolid block
$d="([!#])"; #Regex for solid block
@s=split/,/,'Drop,Drop (Stand),Stepx,Climb offx,Shufflex,Clamber Upx,Put your back into it!x,Punchx,Kickx,Short Jumpx,Long Jumpx,High Jumpx,Crouch,Stand,Climb Up,Stamp'; #Movement names
@c=split/,/,"o$c\\|$c$n/ \$1o\$2|,%$c$n/o\$1|,o$n$h\\|$n$h${d}/ o\$1 |\$2\$3,=${c}o$n$h\\|$n$h=/=\$1=o\$2=|\$3=,%$n$h$d/ %\$1\$2,o$n$h\\|${d}$h${d}/ %".'$1 $2$3$4,'."o!$n$i\\|!$n$i${d}/  o\$1  |\$2\$3,o!$h\\|$c$d/o \$1|\$2\$3,\\|!$h$d/| \$1\$2,o$n$n$i\\|$n$n$i${d}/  o\$1  |\$2\$3,o$n$n$n$j\\|$n$n$n$j${d}/   o\$1   |\$2\$3,$n$n${h}o$n$h\\|$c$d/ o".'$1 |$2 $3$4,'."o$c\\|$c${d}/ \$1%\$2\$3,$n$c%$c${d}/o\$1|\$2\$3,=${c}o$c\\|/o\$1|\$2=,\\|$c!/|\$1 ";#Movement regexes
eval"*$_=sub{\$Q=\"$s[$_]\";s/$c[$_]/}"for(0..15); #Setup methods to apply regexes. Name of these methods are 0,1,2,3, etc, so we can easily call them in a loop
sub M{$_=join":",map{join'',reverse split//}split/:/}; #Method to mirror the map. All the regexes are right-moving, so the mirror effects are achieved by M;&$x;M
push@M,[$_,0,100,$P]; #Array for initial map position. Encodes: [Map,Effort value,Probability value 1,Probability value 2,Movements(initially undef)]. Two integers are used for the probability to avoid floating point (although after enough steps they overflow and are automatically converted to f.p.)
$B{$_}=100; #Hash map to hold best probability of reaching a map. A new map is never added if it requires at least as much effort and doesn't give a better probability.
@E=(0,0,1,1,3,10,20,8,8,4,7,12,2,4,2,8); #Effort values
@P=map{100-$_}(0,0,0,0,0,5,20,10,15,5,25,10,0,0,0,10); #Probability values
$z='$N=[$_,$G+0,$t,$t[3]*100,"$t[4]$Q\n"];$N->[4]=~s/x/'; #Setup map values
do{ #Loop over all efforts. Do-while loop starts at undef, which is converted to zero automatically when used in a numeric context
    sub A{@N=@$N;if($N[2]/$N[3]>$B{$N[0]}){push@M,$N;$B{$N[0]}=$N[2]/$N[3]}}; #Method to add a map to list.
    die"There is no hope!\n"if(!(@M=grep$G-$_->[1]<21,@M)); #Pares away maps that no longer can produce new maps, and prints "There is no hope!" to stderr if there are no maps left.
    $e=-1;
    while(++$e<@M){ #Loop over all maps. Note that this loops over even maps that are created during the loop
        @t=@{$M[$e]}; #Dereference info about each map
        $m=$_=$t[0]; $Setup map variables
        die"Required Effort: $t[1]\n$t[4]"if(/([|%]:|:[|%])/||/[|%][^:]*$/||/^$c:[^:]*[%|]/); #Checks if escaped, and gives message if so.
        for$x(0..15){
            $_=$m; #Put map into $_
            $t=$t[2]*$P[$x]; #Probability calculation
            if($G==$E[$x]+$t[1]and$t>=$t[3]*100){ #If effort values are right and probability is over threshold
                &$x; #Run regex
                eval"$z Right/"; #Set up map info
                A; #Add map to list @M (only if probability works out right)
                $_=$m;
                M;&$x;M; #Same thing, but mirrored now (generates movement left)
                eval"$z Left/";
                A;
            }
        }
    }
}while(++$G)

我已经可以看到几个地方可以减少一些字节,但是我不想再次进行所有测试。后来!:)

编辑:在下面也添加了一些填充,以便在逃离地板时更精确地适合您的输出。删除了一些评估,所以代码现在也可能运行得更快!

编辑:没有处理梯子和下降很正确。仍然不能正确地处理梯子...现在尝试修复该问题。


很高兴其他人玩得开心!恐怕我不能接受当前的状态,因为您不能假设输入的行顶部有额外的行或填充(但在〜1.5kB时,直接插入不会影响太多了!)。我在这台机器上没有Perl,但是我将尝试找到一种今天进行测试的方法,因此我可以检查它是否可以正常工作并在合理的时间范围内运行,并进行报告!
VisualMelon'5

1
@VisualMelon没问题,我更改了输入法并手动添加了填充。它确实会在较大的难题中爆炸,但是对于大多数测试用例而言,它运行的时间是合理的。
克里斯(Chris)

仍然没有经过测试,但是我注意到您说过,它使用正则表达式来检测何时离开侧面或底部,但是您也可以脱离顶部(请参阅为此目的添加的testcase10),以防万一您错过了这
VisualMelon

1
@VisualMelon啊,我想逃脱者必须逃离屋顶,然后从屋顶走到一边。我现在看到相关的句子。我会解决的:)
克里斯(Chris)

2
您可以添加一个TIO链接吗?
程序员

3

C#,1814 1481 1395字节

真是太遗憾了!我现在真的很高兴!

using C=System.Console;using System.Linq;class S{string M,N="";int x,y,E,c;decimal P=1;static void Main(){int W=0,H=0,x,i,j,k,X,Y,f,m,P=int.Parse(C.ReadLine());string l,M="",B,R="There is no hope!";for(;(l=C.ReadLine())!=null;H++,M+=l)W=l.Length;x=M.IndexOf("|");var D=new[]{new S{M=M,x=x%W,y=x/W}}.ToList();for(var N=D.ToDictionary(s=>R,s=>D);D.Count>0;D.Sort((z,w)=>z.E-w.E)){S s=D[f=0];D.Remove(s);var n=N[l=s.x+s.M+s.y+s.c]=N.ContainsKey(l)?N[l]:new S[0].ToList();if(n.All(o=>o.P<s.P|o.E>s.E)){n.Add(s);X=s.x;Y=s.y;if((X|Y|-X/W-Y/H)<0){R="Required Effort: "+s.E+s.N;break;}for(var A="0110d,Step,*** * #*,0110d,Climb off,=** * =*,3310d,Shuffle,***** #*,2:1/_,Clamber Up,*** *##*,0420_,Short Jump,****  *  #**,0730K,Long Jump,*****   *   #***,0<1/Z,High Jump,  * **#*,0D20P,Put your back into it!,****! *! #**,0800Z,Punch,***!**#*,0800U,Kick,*****!#*,0001d,Drop,*** ,1001d,Drop (Stand),*** ,2200d,Crouch,***#,1400d,Stand,* *#,020/d,Climb Up,=***,0800Z,Stamp,***!".Split(',');f<26;){l=A[k=f*3%48];B=A[++k]+(f>15?" Right":f>9?"":" Left");M=A[++k];m=f++/16*2-1;var Q=s.M.ToArray();var K=s.P*l[4]>=P&s.c==l[k=0]%2;for(j=Y-3;j++<=Y;)for(i=X;i!=X+m*M.Length/4;i+=m)if((K&="==  *!!##*!*=*|*| o*o ".Contains(""+((i|j)>=0&j<H&i<W?Q[x=j*W+i]:' ')+M[k]))&M[k++]==33)Q[x]=' ';if(K)D.Add(new S{M=new string(Q),N=s.N+"\n"+B,x=X+l[2]%48*m,y=Y+l[3]-48,c=l[0]/50,E=s.E+l[1]-48,P=s.P*l[4]/100});}}}C.Write(R);}}

在线试用

完整的程序,将输入输入到STDIN,将输出输入到STDOUT。本质上是使用简单且效率低下的BFS查找最佳路径来重写我的原始求解器。它相当快,不会比我的其他实现慢很多(不过我还没有真正计时),当然可以在时限内运行。大部分费用是动作表,该表编码为逗号分隔的值,该值记录名称,“匹配/粉碎”图以及每个可用动作的其他属性。

它从读取成功的最小可能性和图开始。然后找到逃犯,将其从地图上移走,并创建一个包含此信息的“搜索状态”。然后,它执行BFS,它以最小的努力重复查看下一个到期状态(确保找到最佳解决方案)。在扩展节点之前,它会将其努力和成功概率与具有相同地图和逃逸者位置的先前节点进行比较,如果已经找到到该状态的更好的路线,则拒绝自身。如果它能够幸免,它将自己添加到“可见”表中,以便以后可以拒绝状态。这一切都是为了提高性能,没有它,分支因素将是巨大的。然后,它检查逃生者是否不在地图上。如果是,那就赢了!在退出BFS循环之前,它会回溯状态(每个先前的状态与每个状态一起记录)并构建计划(相反)。否则,它会尝试应用所有操作,然后将所有可以应用到due 队列,在对该队列进行排序之前,我们可以在下一次迭代中获得最少的省力状态。

格式化和注释的代码:

using C=System.Console;
using System.Linq;

// ascii
//   32
// ! 33
// = 61
// . 46
// * 42
// # 35
// | 124
// 0 48

class S // SearchState
{
    string M, // map
        N=""; // plan
    int x, // pos x
        y, // pos y
        E, // effort
        c; // crouching?
    decimal P=1; // chance (Probability)

    static void Main()
    {
        int W=0, // map width
            H=0, // map height
            x, // various temps
            i, // local x
            j, // local y
            k, // position in match/smash map
            X, // s.x
            Y, // s.y

            // action loop
            f, // T idx
            m, // A idx, action mirror (direction)

            P=int.Parse(C.ReadLine()); // probability of success constraint

        string l, // line, Act 'block' params, map hash, match pairs; all sorts!
            M="", // initial map
            B, // name of action
            R="There is no hope!"; // result string

        // read map
        for(;(l=C.ReadLine())!=null; // while there is input to read
            H++,M+=l) // increment height, and append to M
            W=l.Length; // record width

        // detect the escapee
        x=M.IndexOf("|");

        // create first state, and add it to Due list
        var D=new[]{new S{M=M,x=x%W,y=x/W}}.ToList();

        // 'seen' table, stores all the states we've been in which looked similar
        for(var N=D.ToDictionary(s=>R,s=>D); // these values are meaningless (and necessarily can't interfere), we just use it to save having to spec the type
            D.Count>0; // keep going until we run out of states to expand (-> no escape)
            D.Sort((z,w)=>z.E-w.E)) // enforce Breadth First Search
        {
            // get next State to expand, and remove it from Due
            S s=D[f=0];
            D.Remove(s);

            // retrieve or create seen list
            var n=N[l=s.x+s.M+s.y+s.c]= // store it, and cache it, l is 'hash', containing map and escapee state
                N.ContainsKey(l)? // already got a seen list for ths map?
                N[l]: // then use it!
                new S[0].ToList(); // otherwise create a new (empty) one

            // perf: only proceed if we havn't seen this map with better Effort and Chance yet
            if(n.All(o=>o.P<s.P|o.E>s.E))
            {
                // record that we have been seen
                n.Add(s);
                X=s.x;
                Y=s.y;

                if((X|Y|-X/W-Y/H)<0) // check if we our outside the bounds - this took 2.5hour to write. 1 byte.
                { // quit if both are positive or both are negative
                    // freedom
                    R="Required Effort: "+s.E+s.N;

                    // finished
                    break;
                }

                // [Crouch,Effort,dx,dy,Probability],Name,MatchSmash (first 10 are mirrors)
                // 0110d,Step,*** * #*,
                // 0110d,Climb off,=** * =*,
                // 3310d,Shuffle,***** #*,
                // 2:1/_,Clamber Up,*** *##*,
                // 0420_,Short Jump,****  *  #**,
                // 0730K,Long Jump,*****   *   #***,
                // 0<1/Z,High Jump,  * **#*,
                // 0D20P,Put your back into it!,****! *! #**,
                // 0800Z,Punch,***!**#*,
                // 0800U,Kick,*****!#*,
                // 0001d,Drop,*** ,
                // 1001d,Drop (Stand),*** ,
                // 2200d,Crouch,***#,
                // 1400d,Stand,* *#,
                // 020/d,Climb Up,=***,
                // 0800Z,Stamp,***!

                // attempt to expand this node with every action
                for(var A="0110d,Step,*** * #*,0110d,Climb off,=** * =*,3310d,Shuffle,***** #*,2:1/_,Clamber Up,*** *##*,0420_,Short Jump,****  *  #**,0730K,Long Jump,*****   *   #***,0<1/Z,High Jump,  * **#*,0D20P,Put your back into it!,****! *! #**,0800Z,Punch,***!**#*,0800U,Kick,*****!#*,0001d,Drop,*** ,1001d,Drop (Stand),*** ,2200d,Crouch,***#,1400d,Stand,* *#,020/d,Climb Up,=***,0800Z,Stamp,***!".Split(','); // Act string
                    f<26;) // read A into T // (cycle over first 10 twice, making them mirrors)  (very convieniently, 3*16==48=='0'==x!)
                {
                    // 'parse' next action
                    l=A[k=f*3%48]; // [Effort,Crouch,dx,dy,Probability]
                    B=A[++k]+(f>15?" Right":f>9?"":" Left"); // action name
                    M=A[++k]; // Match and Smash table (4x?, escapee at 0,2, #: solid, !: smashable (and is smashed), .: empty,  : don't care, =: climable)
                    //c=l[1]-48; // crouching transition (0: standing -> standing, 1: crouching -> standing, 2: standing -> crouching, 3: crouching -> crouching)
                    m=f++/16*2-1;

                    // this will be the new map
                    var Q=s.M.ToArray();

                    // K is whether we are allowed to apply this action
                    var K=s.P*l[4]>=P& // within chance limit
                        s.c==l[k=0]%2; // crouching matches

                    // compare the map to the Match/Smash map, to make sure we can apply this transform (and smash any ! we meet)
                    for(j=Y-3;j++<=Y;) // for the 4 rows
                        for(i=X;i!=X+m*M.Length/4;i+=m) // for each column (a.M has height 4, but variable width)
                            if((K&= // K still true?
                                "==  *!!##*!*=*|*| o*o ".Contains(""+ // check for an allowed pairing (cache pairing in M) (this includes the escapee, much cheaper to do this than remove him)
                                    ((i|j)>=0&j<H&i<W?Q[x=j*W+i]:' ') // we are within bounds and match, we are out of bounds (empty)
                                    +M[k])) // char from MatchSmash map
                                &M[k++]==33) // if it is destructible ('!' == 33)
                                Q[x]=' '; // then blank it out (x is necessarily set by the cell check)

                    if(K) // if K holds
                        D.Add(new S{M=new string(Q),N=s.N+"\n"+B, // assemble plan as we go (this will chew up memory, but none of the problems are big enough for it to matter)
                            x=X+l[2]%48*m,y=Y+l[3]-48,c=l[0]/50,E=s.E+l[1]-48,P=s.P*l[4]/100}); // add the resulting state to Due
                }
            }
        }

        C.Write(R);
    }
}

不错的工作!你们的旧乐谱和新乐谱互为字谜,这让我无休止地感到
开心

C#击败了Perl吗?
硕果累累
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.