那细菌去哪儿了?


21

介绍

您是一位研究细菌运动模式的生物学家。您的研究团队在培养皿中有一堆,您正在记录它们的活动。不幸的是,您的资金严重不足,而且买不起摄像机,因此您只需要定期为盘子拍照。您的任务是创建一个程序,以跟踪这些图片中细菌的运动。

输入项

您的输入是两个2D字符数组,它们以任何合理的格式表示皮氏培养皿的连续图片。在这两个数组中,字符.代表空白空间,并O代表细菌(可以根据需要选择任意两个不同的字符)。同样,通过在四个基本方向之一上将某些细菌移动一步,从“之前”阵列中获得“之后”阵列。特别地,阵列具有相同的形状。细菌会同时移动,因此如果其中一个细菌移开了,它们中的一个可能会移动到已经包含另一种细菌的空间中。确保“ before”数组的边界仅包含空白空间,并且至少有一个病菌。因此,以下是一对有效的输入:

Before  After
......  ......
.O..O.  ....O.
.OO.O.  .OO.O.
......  ..O...

输出量

您的输出是单个2D字符数组,其格式与输入相同。它是从“之前”阵列中获得的>^<v,方法是根据移动的方向(使用移动的4个不同的字符)替换其中一个已移动的细菌。可能会有几种输出,但是您只能给出其中一种。在上面的示例中,一种可能的正确输出是

......
.v..O.
.>v.O.
......

输出中允许不必要的移动,并且细菌可以交换位置,因此以下内容也有效:

......
.v..v.
.>v.^.
......

规则和计分

您可以编写完整的程序或函数。最低字节数获胜,并且不允许出现标准漏洞。

我对相对高效的算法感兴趣,但我不想完全禁止蛮力。因此,在现代CPU上在10分钟内解决最后一个测试用例时,可获得-75%奖励(我无法测试大多数解决方案,因此在这里我会信任您)。免责声明:我知道存在一种快速算法(搜索“不相交路径问题”),但是我自己没有实现它。

其他测试用例

Before
......
.O..O.
..OO..
......
After
......
..O...
...OO.
..O...
Possible output
......
.>..v.
..vO..
......

Before
.......
.OOOOO.
.O..OO.
.OO..O.
.OOOOO.
.......
After
.......
..OOOOO
.O...O.
.O...O.
.OOOOOO
....O..
Possible output
.......
.>>>>>.
.O..>v.
.Ov..v.
.O>>v>.
.......

Before
..........
.OOO..OOO.
.OOOOOOOO.
.OOO..OOO.
..........
After
..O.......
.OOO..O.O.
..OOOOOOOO
.O.O..OOO.
.......O..
Possible output
..........
.>^O..O>v.
.^O>>>vO>.
.O>^..>vO.
..........

Before
............
.OO..OOOOOO.
.OO......OO.
...OOOOOO...
.O.OOOOOO.O.
...OOOOOO...
.OOOOOOOOOO.
............
After
..........O.
.OO..OOOOO..
.O...O...O..
.O.OOOOOOO..
.O.OOOOOO..O
...OO..OO...
....OOOOOOOO
.OOO........
Possible output
............
.OO..v<<<<^.
.v<......^<.
...OOO>>>...
.O.OOO^OO.>.
...OOv^OO...
.vvvO>>>>>>.
............

Before
................
.OOOOOO.OOOOOOO.
..OO..OOOOOOOOO.
.OOO..OOOO..OOO.
..OOOOOOOO..OOO.
.OOOOOOOOOOOOOO.
................
After
................
..OOOOO.OOOOOOOO
..OO..OOOOOOOOO.
..OO..OOOO..OOOO
..OOOOOOOO..OOO.
..OOOOOOOOOOOOOO
................
Possible output
................
.>>>>>v.>>>>>>>.
..OO..>>^>>>>>v.
.>>v..OOO^..OO>.
..O>>>>>>^..OOO.
.>>>>>>>>>>>>>>.
................

Before
..............................
.OOO.O.O.....O.....O.O.O..O...
..OOO.O...O..OO..O..O.O.......
.....O......O..O.....O....O...
.O.OOOOO......O...O..O....O...
.OO..O..OO.O..OO..O..O....O...
..O.O.O......OO.OO..O..OO.....
..O....O..O.OO...OOO.OOO...O..
.....O..OO......O..O...OO.OO..
........O..O........OO.O.O....
..O.....OO.....OO.OO.......O..
.O.....O.O..OO.OO....O......O.
..O..OOOO..O....OO..........O.
.O..O...O.O....O..O....O...OO.
....O...OO..O.......O.O..OO...
........O.O....O.O....O.......
.OO.......O.OO..O.......O..O..
....O....O.O.O...OOO..O.O.OO..
.OO..OO...O.O.O.O.O...OO...O..
..............................
After
..............................
.OOOOO.......OO.....O..O......
...OO..O...O...O....OO....O...
....O.O......O..OO...OO...O...
.OO.OOOO......OO..O..O........
O.O.OO..O..O..O..OO...O...OO..
.OO.....O....OO.O..O.OO.O.....
......O.....O.....OOO.OO...O..
....O..OOOO..O..O..O.O.O.OO...
..O......O.O........O...O.O...
.O.....OOO.....OO.OO...O...O..
.......OOO..O.O.O...........O.
.O...O.....O...OOOO..O.O....O.
.O..O.O..O.....O......O....OO.
....O..O..O.O......O.....O....
........OOO....O......O..O....
.OO......O..OO..OOO.....O..O..
..O.O....OO..O...OO...O...OO..
.O..OO....O..O...O.O.O.OO.....
..............O............O..
Possible output
..............................
.OOO.O.v.....>.....>.v.O..v...
..>>^.v...>..^>..v..O.v.......
.....<......>..>.....O....O...
.O.<O><O......O...O..O....v...
.<O..O..v<.O..O^..O..>....>...
..<.^.v......OO.O^..>..<O.....
..^....v..v.Ov...>>^.<OO...O..
.....<..OO......O..O...Ov.v<..
........>..O........O^.v.^....
..^.....Ov.....OO.OO.......O..
.^.....^.^..O>.vO....v......O.
..<..Ov^^..O....><..........O.
.O..O...>.v....O..^....^...OO.
....O...<v..O.......<.^..v<...
........O.O....O.v....O.......
.OO.......<.Ov..O.......O..O..
....O....O.<.^...O^v..O.v.OO..
.O^..<<...O.>.v.>.^...<O...v..
..............................

可以肯定的是,细菌只能移动一个或零个细胞,对吗?
Domino

@JacqueGoupil是的,这是正确的。每一个都>^<v对应于在相应方向上精确移动一个台阶。
Zgarb

我还没有尝试解决它,但是这是一个构建更多测试用例的工具:) jsfiddle.net/xd2xns64/embedded/result
Domino

哦,小心,如果脚本尝试将所有单元格移向边缘,则脚本将永远循环,但是边缘单元无处可去。
2015年

Answers:


3

八度,494个 496字节- 372字节奖金= 124个字节

function o=G(b,a)
y='.O<^v>';s=(b>46)+0;t=a>46;v=t;f=s;t(:,2:end,2)=t(:,1:end-1);t(2:end,:,3)=t(1:end-1,:,1);t(1:end-1,:,4)=t(2:end,:,1);t(:,1:end-1,5)=t(:,2:end,1);t=reshape(t,[],5);m=size(s,1);p=[0 -m -1 1 m];
function z(n)
f(n+p(s(n)))--;q=find(t(n,:));w=n+p(q);d=min(f(w));q=q(f(w)==d);j=randi(numel(q));s(n)=q(j);f(n+p(q(j)))++;end
for g=find(s)' z(g);end
while any((f~=v)(:)) L=find(s);k=zeros(size(s));for h=L' k(h)=f(h+p(s(h)));end;c=find(k>1);g=c(randi(numel(c)));z(g);end
o = y(s+1);end

关于这个答案,仍然有很多高尔夫运动要做,但是我想得到一些高尔夫球手的解释。

我将其视为“约束满足问题”,因此我选择了最喜欢的本地搜索启发式“ 最小冲突”。这个想法是,假设每个细菌都开始放置在可到达的目标位置,选择一个随机细菌,该随机细菌与一个或多个其他细菌占据相同的目标细胞,然后将其移动到已存在最少其他细菌的有效细胞中。根据需要重复,直到展示位置与目标匹配为止。

有趣的是,这种算法不能保证终止(例如,如果目标无法达到,它将无限期地继续),但是如果算法终止,则可以保证产生有效的解决方案。

这是代码:

function output = germs(before, after)

%before = ['......';'.O..O.';'.OO.O.';'......'];
%after = ['......';'....O.';'.OO.O.';'..O...'];

symbs = '.O<^v>';
start = (before > 46) + 0;                   %should be called current_board
target = after > 46;                         %destinations on current cell == O
goal = target;
conflicts = start;
target(:, 2:end,2) = target(:, 1:end-1);     %destinations on cell to left
target(2:end, :,3) = target(1:end-1, :,1);   %destinations on cell above
target(1:end-1, :,4) = target(2:end, :,1);   %destinations on cell below
target(:, 1:end-1,5) = target(:, 2:end,1);   %destinations on cell to right
target=reshape(target,[],5);
m = size(start,1);                           %number of rows = offset to previous/next column
offsets = [0 -m -1 1 m];                     %offsets of neighbors from current index


function moveGerm(n)
   conflicts(n+offsets(start(n)))--;         %take germ off board
   move = find(target(n, :));                %get valid moves for this germ
   neighbors = n + offsets(move);            %valid neighbors = current position + offsets
   minVal = min(conflicts(neighbors));       %minimum number of conflicts for valid moves
   move = move(conflicts(neighbors)==minVal);
   mi = randi(numel(move));                  %choose a random move with minimum conflicts
   start(n) = move(mi);                      %add move type to board
   conflicts(n + offsets(move(mi)))++;       %add a conflict on the cell we move to
end

% Generate an initial placement
for g = find(start)'
   moveGerm(g);                              %make sure all germs are moved to valid cells
end

% Repeat until board matches goal
while any((conflicts ~= goal)(:))
   germList = find(start);                   %list of all our germs
   cost = zeros(size(start));                %calculate conflicts for each germ
   for h = germList'
      cost(h) = conflicts(h + offsets(start(h)));
   end
   conflicted = find(cost > 1);              %find those germs that occupy the same cell as another
   g = conflicted(randi(numel(conflicted))); %choose a random germ to move
   moveGerm(g);
end

output = symbs(start+1);                     %use moves as indices into symbol array for output

end

最后一个测试用例的输出:

>> gtest
ans =

..............................
.OO>.O.v.....>.....>.v.O..v...
..>^O.v...>..^>..v..O.v.......
.....v......>..>.....O....O...
.O.<^<OO......>...O..O....v...
.<O..O..v<.O..^<..O..>....>...
..<.^.v......OO.O^..<..<O.....
..^....v..v.Ov...>>>.^OO...O..
.....<..OO......O..O...Ov.<<..
........>..O........O^.v.>....
..^.....OO.....OO.OO.......O..
.^.....^.O..O>.vO....v......O.
..<..Ov^^..O....OO..........O.
.O..O...>.v....O..^....^...OO.
....O...<v..O.......<.^..v<...
........O.O....O.v....O.......
.OO.......<.OO..O.......O..O..
....O....O.<.O...O^<..O.v.OO..
.O^..<<...O.>.v.>.>...<O...v..
..............................

Elapsed time is 0.681691 seconds.

5岁的Core i5 的平均经过时间少于9秒 1秒*,有资格获得赠金。

我正在尝试在ideone上进行此工作,但是我认为它在处理嵌套函数的方式方面存在问题。(以下是无法使用的ideone链接,以供参考:http : //ideone.com/mQSwgZ ideone
上的代码现在可以正常工作。我不得不将所有变量都强制设置为全局变量,而不必在本地运行它。

*我在非高尔夫版本中注意到一个步骤效率低下,因此我尝试查看它是否可以加快执行速度,并且每增加2个字节,执行时间现在减少到不到一秒钟。代码和示例输出已更新,并且在ideone上的输入已更改为最后一个测试用例。


3

Python,1171字节-878.25字节奖励= 292.75字节

from itertools import *;from random import *;R=range;L=len;O=choice;G='O'
def A(a,b):a.append(b)
def D(y,z):
 a=[];b=[];c=[]
 for i in R(L(y)):
  A(c,[])
  for j in R(L(y[0])):
   k=[(i,j),y[i][j]==G,z[i][j]==G,[],0];A(c[i],k)
   for l,m in [(0,1),(1,0)]:
    try:
     n=c[i-l][j-m]
     if k[2]&n[1]:A(n[3],k)
     if k[1]&n[2]:A(k[3],n)
    except:pass
   if k[1]&~k[2]:A(a,k)
   elif k[2]&~k[1]:A(b,k)
 d={}
 for i in a:
  j=[[i]]
  while j:
   k=j.pop();l=[e[0] for e in k]
   while True:
    m=k[-1];n=[o for o in m[3] if o[0] not in l]
    if not n:
     if m in b:A(d.setdefault(i[0],[]),k)
     break
    for o in n[1:]:p=k[:];A(p,o);A(j,p)
    A(k,n[0]);A(l,n[0][0])
 e={}
 for i in a:e[i[0]]=O(d[i[0]])
 def E():return sum(any(k in j for k in i) for i,j in combinations(e.values(),2))
 f=E()
 for i in count():
  t=3**-i/L(a);j=O(a);k=e[j[0]];e[j[0]]=O(d[j[0]]);l=E()
  if not l:break
  else:
   if l>f and random()>t:e[j[0]]=k
   else:f=l
 for i in e.values():
  for j in R(L(i)-1):i[j][4]=i[j+1]
 for i in c:
  for j in R(L(i)):
   k=i[j]
   if 1&~k[1]:i[j]='.'
   elif not k[4]:i[j]=G
   else:l,m=k[0];n,o=k[4][0];i[j]='v>^<'[abs((l-n+1)+2*(m-o))]
 return c

Ideone链接:http ://ideone.com/0Ylmwq

在最后一个测试用例上平均需要花费1-8秒的时间,有资格获得奖金。

这是我第一次提交代码高尔夫球,因此它可能不是最佳高尔夫球程序。但是,这是一个有趣的挑战,我非常喜欢。@Beaker值得一提,提醒我基于启发式的搜索是一回事。在他发布解决方案并激发我重做我的答案之前,我的蛮力搜索时间太长,无法获得最后一个测试用例的奖金(大约69次迭代,这是一个99位数字。) )。

我不想直接复制Beaker的解决方案,因此我决定将模拟退火用于搜索启发式。这个问题似乎比最小冲突要慢(可能是因为它是一种优化算法,而不是约束满足算法),但仍在10分钟以内。从代码角度来看,它还具有相当小的优势。与解决问题相比,我在解决问题上花费了更多的精力。

说明

我的解决方案可能是字节效率低下的,但是我很难在概念上解决如何按原样解决问题,因此最终不得不将其转换为一个更容易理解的问题。我意识到网格上的每个单元格都有四个可能性:

  • 它之前或之后都没有细菌,这意味着我们可以忽略它
  • 它之前有病菌,但之后没有病菌,这意味着我们必须找到一个办法。
  • 它以前没有病菌,但只有一个病菌,这也意味着我们必须为此寻找一个行动。
  • 它在前后都有细菌,这意味着我们可能必须为此寻找一个招,但随后也许并非如此。

将数据分解成这些类之后,我就能够进一步解决问题。对我而言,立即显而易见的是,我必须找到一种方法,将“之前但不之后”的细菌提供给“之后但不之前”的细菌。此外,细菌只能在一个空间中移动,因此,它们影响更远的细胞的唯一方法是将细菌的不间断路径“推入”该细胞。这意味着问题变成了在图上找到X个顶点不相交的路径,其中每个带有细菌的单元格都是该图中的一个顶点,而边表示相邻的单元格。

通过首先构建上面解释的图,我解决了这个问题。然后,我枚举了Before中每个单元格和After中每个单元格的所有可能路径,然后随机分配了Before中每个单元格的可能路径之一。最后,我使用模拟退火对潜在解决方案进行了半随机变异,直到最终找到与任何路径都没有冲突的解决方案。

带注释的版本

from itertools import *;from random import *;

# redefine some built-in functions to be shorter
R=range;L=len;O=choice;G='O'
def A(a,b):a.append(b)

# The function itself.  Input is in the form of two 2d arrays of characters, one each for before and after.
def D(y,z):
 # Declare the Before-but-not-after set, the After-but-not-before set, and a temp cell array
 # (the cells are temporarily stored in a 2d array because I need to be able to locate neighbors)
 a=[];b=[];c=[]

 # Build the graph
 for i in R(L(y)):
  # Append a row to the 2d temp array
  A(c,[])

  for j in R(L(y[0])):
   # Define the interesting information about the cell, then add it to the temp array
   # The cell looks like this: [position, does it have a germ before?, does it have a germ after?, list of neighbors with germs, final move]
   k=[(i,j),y[i][j]==G,z[i][j]==G,[],0];A(c[i],k)
   for l,m in [(0,1),(1,0)]:
    # Fill up the neighbors by checking the above and left cell, then mutually assigning edges
    try:
     n=c[i-l][j-m]
     if k[2]&n[1]:A(n[3],k)
     if k[1]&n[2]:A(k[3],n)
    except:pass

   # Decide if it belongs in the Before or After set
   if k[1]&~k[2]:A(a,k)
   elif k[2]&~k[1]:A(b,k)

 # For each cell in the before set, define ALL possible paths from it (this is a big number of paths if the grid is dense with germs)
 # This uses a bastard form of depth-first search where different paths can cross each other, but no path will cross itself
 d={}
 for i in a:
  j=[[i]]  # Define the initial stack of incomplete paths as the starting node.
  while j:
   # While the stack is not empty, pop an incomplete path of the stack and finish it
   k=j.pop();l=[e[0] for e in k]
   while True:
    # Set the list of next possible moves to the neighbors of the current cell,
    # ignoring any that are already in the current path.
    m=k[-1];n=[o for o in m[3] if o[0] not in l]

    # If there are no more moves, save the path if it ends in an After cell and break the loop
    if not n:
     if m in b:A(d.setdefault(i[0],[]),k)
     break

    # Otherwise, set the next move in this path to be the first move,
    # then split off new paths and add them to the stack for every other move
    for o in n[1:]:p=k[:];A(p,o);A(j,p)
    A(k,n[0]);A(l,n[0][0])

 # Perform simulated annealing to calculate the solution
 e={}
 for i in a:e[i[0]]=O(d[i[0]])  # Randomly assign paths for the first potential solution

 # Define a function for calculating the number of conflicts between all paths, then do the initial calculation for the initial potential solution
 def E():return sum(any(k in j for k in i) for i,j in combinations(e.values(),2))
 f=E()

 # Do the annealing
 for i in count():
  # The "temperature" for simulated annealing is calculated as 3^-i/len(Before set).
  # 3 was chosen as an integer approximation of e, and the function e^(-i/len) itself was chosen because
  # it exponentially decays, and does so slower for larger problem sets
  t=3**-i/L(a)

  j=O(a)              # Pick a random Before cell to change
  k=e[j[0]]           # Save it's current path
  e[j[0]]=O(d[j[0]])  # Replace the current path with a new one, randomly chosen
  l=E()               # Recalculate the number of conflicts

  if not l:break  # If there are no conflicts, we have a valid solution and can terminate
  else:           # Otherwise check the temperature to see if we keep the new move
   if l>f and random()>t:e[j[0]]=k  # Always keep the move if it's better, and undo it with probability 1 - T if it's worse
   else:f=l                         # If we don't undo, remember the new conflict count

 # Set each of the cells' final moves based on the paths
 for i in e.values():
  for j in R(L(i)-1):i[j][4]=i[j+1]

 # Build the output in the form of a 2d array of characters
 # Reuse the temp 2d array from step since its the right size
 for i in c:
  for j in R(L(i)):
   k=i[j]
   # Cells that are empty in the before array are always empty in the output
   if 1&~k[1]:i[j]='.'
   # Cells that aren't empty and don't have a move are always germs in the output
   elif not k[4]:i[j]=G
   # Otherwise draw the move
   else:l,m=k[0];n,o=k[4][0];i[j]='v>^<'[abs((l-n+1)+2*(m-o))]
 return c
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.