Selos de cientistas presos em um iceberg


17

Introdução

Uma família de focas está presa em um iceberg no Círculo Polar Ártico. Há um transmissor de rádio localizado no iceberg que os selos podem usar para pedir ajuda. No entanto, apenas o selo papai sabe como operar o transmissor de rádio. E pior, o gelo é muito escorregadio nesta época do ano, de modo que as vedações deslizam incontrolavelmente até atingirem outro selo ou deslizarem para fora da borda do iceberg, dificultando muito o acesso do selo papai ao transmissor de rádio. Felizmente, um dos selos é um cientista da computação, então ela decide escrever um programa para descobrir como manobrar o selo do papai no transmissor de rádio. Como não há muito espaço no gelo para escrever um programa, ela decide fazer com que o programa use o mínimo de bytes possível.

Descrição da entrada

O programa do selo receberá entradas do STDIN, argumentos da linha de comandos ou funções de entrada do usuário (como raw_input()). Não pode ser pré-inicializado em uma variável (por exemplo, "Este programa espera a entrada em uma variável x").

A primeira linha da entrada consiste em dois inteiros separados por vírgula no formato

A,B

A seguir, são Blinhas compostas por Acaracteres cada. Cada linha pode conter apenas caracteres dentre os seguintes:

  • .: O frio, frio, oceano. O mapa sempre terá isso como uma borda.
  • #: Uma parte do iceberg.
  • a... z: Um selo que não é o selo do papai no iceberg.
  • D: O selo do papai no iceberg.
  • *: O transmissor de rádio.

(Observe que o selo do papai é sempre anotado com uma letra maiúscula D. Uma minúscula dé simplesmente um selo regular.)

Descrição da saída

De acordo com as regras a seguir sobre como os selos podem se mover, envie uma lista de instruções para os selos sobre como eles devem se mover para obter o selo do papai no transmissor de rádio.

  1. Regra: Todos os selos podem se mover para cima ( U), para baixo ( D), para a esquerda ( L) e para a direita ( R). Eles não podem deslizar na diagonal.
  2. Regra: Ao se mover, um selo continuará se movendo na mesma direção até colidir com outro selo ou cair no mar.
    1. Se um selo colidir com outro, ele irá parar de se mover. O selo com o qual colidiu não se moverá.
    2. Se um selo cair no mar, ele se afogará e desaparecerá do mapa. Ou seja, ele não atua como colisor para outros selos e não pode ser movido novamente.
  3. Regra: Dois selos não podem se mover ao mesmo tempo, nem um selo pode ser movido enquanto outro ainda está em movimento. O próximo selo só pode ser movido quando o selo anterior parar de se mover.
  4. Regra: Não há restrição quanto à movimentação de um selo várias vezes ou ao número de selos que se afogam.
  5. Regra: Uma solução correta fará com que o selo do papai termine no transmissor de rádio. O selo do papai não pode simplesmente passar pelo transmissor enquanto desliza.

A saída consistirá em várias linhas, cada uma na forma

A,B

Onde Aé o selo para mover ( Dpara o selo pai, a... zpara os outros), e Bé a direção para mover o selo (ou U, D, Lou R). Observe que você não precisa encontrar a rota mais curta. Qualquer rota que leve o selo do papai à meta é uma saída aceitável.

Exemplo de entradas e saídas

Entrada:

25,5
.........................
.#######################.
.####D#############*k###.
.#######################.
.........................

Resultado:

D,R

Entrada:

9,7
.........
.a#####b.
.#####d#.
.##l*###.
.###m#p#.
.#D#.#c#.
.........

Saída (uma saída possível dentre muitas):

m,R
b,L
D,U
D,R
D,D
D,L

Entrada:

26,5
..........................
.###..................###.
.l*##########v#########D#.
.###..................###.
..........................

Saída (uma saída possível dentre muitas):

v,D
D,L

Se você tiver outras perguntas, pergunte nos comentários.


Todas as entradas terão uma solução válida? Caso contrário, qual é o resultado / comportamento esperado?
Geobits

@ Geobits Todas as entradas terão uma solução válida. Entradas sem soluções são consideradas inválidas e seu programa pode fazer o que quiser com elas.
absinto

É permitido encerrar o programa lançando uma exceção?
DLosc

2
O que acontece se um selo não-papai atingir o transmissor de rádio? Será que vai parar ou passar por isso?
Reto Koradi

11
Isso invalida minha solução. :(
DLosc

Respostas:


6

Python 3, 520 bytes

R=range
g=[list(input())for i in R(int(input().split(',')[1]))]
f=set(sum(g,[]))-set(".#*")
L=8
def P(p,g):
 if len(p)>L:return
 for s in f:
  c=sum(y.index(s)for y in g if s in y)
  if c<1:continue
  r,=[n for n in R(len(g))if s in g[n]]
  for d in R(4):
   m=p+s+",%s\n"%"LURD"[d];G=[y[:]for y in g];o="#";i,j=I,J=r,c
   while"#"==o:G[i][j]="#";G[I][J]=s;i,j=I,J;I,J=i+d%2*(d-2),j+(~d%-2&d-1);o=G[I][J]
   if"."==o:G[i][j]="#"
   if"D"==s:
    if"."==o:continue
    if"*"==o:print(m);1/0
   P(m,G)
while 1:P("",g);L+=4

Posso fazer uma explicação mais detalhada posteriormente, se as pessoas quiserem, mas basicamente isso faz uma pesquisa profunda com aprofundamento iterativo no espaço de estados de possíveis movimentos. Se um movimento faz com que o selo papai caia, ele é imediatamente rejeitado. Se o papai acabar ao lado do transmissor, a sequência de movimentos é impressa e o programa é dividido em zero para forçar uma saída.

Eu posso fazer o código rodar significativamente mais rápido adicionando if G!=g:no início da segunda última linha, por 8 bytes extras - isso rejeita movimentos que não mudam nada, como k,Lno primeiro caso de teste.

O tempo de execução varia visivelmente de uma execução para a seguinte, mesmo com a mesma entrada - evidentemente resultado do fato de eu escolher a próxima vedação para mover iterando sobre a set, que é um tipo não ordenado. Programei o segundo caso de teste em 5 minutos e 30 segundos, embora não parecesse muito tempo na primeira vez em que o executei. Com a otimização mencionada acima, são mais de 40 segundos.


11
Curiosamente, no Python 2, ele deve dar a mesma ordem a cada vez. Eu acho que eles mudaram o Python 3 para fornecer hashes aleatórios em cada execução para os mesmos objetos para evitar certas explorações: "A randomização de hash é ativada por padrão. Defina a variável de ambiente PYTHONHASHSEED como 0 para desativar a randomização de hash. Consulte também o objeto .__ hash __ () método."
Claudiu

4

JavaScript (ES6) 322 334 323

Edit2 Adicionado animação no snippet

Edite Correção de bug, lembre-se da posição inicial de '*', então eu a encontro mesmo quando um selo desliza sobre ela e a apaga.

Implementado como uma função com a string de entrada como parâmetro (provavelmente inválido, mas aguardando um esclarecimento). Saída via pop-up.
O problema com a entrada de cadeia de caracteres de várias linhas no JavaScript é que promptnão o gerencia bem.

Quanto ao algoritmo: um BFS, deve encontrar uma solução ideal. Eu mantenho uma fila de status do jogo em variável l, o status sendo a grade do personagem ge a sequência de movimentos até agora s. Além disso, existe um conjunto de grades obtidas até agora em variável k, para evitar explorar a mesma grade repetidamente.

O loop principal é

  • retirar da fila o status de um jogo
  • tente todos os movimentos possíveis, enfileire o status após cada movimento válido (IIF, a grade resultante ainda não está presente)
  • se a solução for encontrada, saia do loop
F=s=>{
  o=~s.match(/\d+/),g=[...z=s.replace(/.*/,'')];
  for(l=[[g,'']],k=[];[g,s]=l.shift(),!g.some((c,p)=>
      c>'A'&&[-1,1,o,-o].some((d,j)=>{
        t=s+' '+[c,'LRUD'[j]];
        for(q=p;(u=g[q+d])<'.';)q+=d;
        return(c<'a'&z[q]=='*')||
        c>'D'|u>'.'&&!(
          f=[...g],u=='.'?0:f[q]=c,f[p]='#',
          k[h=f.map(v=>v>'D'?0:v)]||(k[h]=l.push([f,t]))
        )
      })
    ););
  alert(t)
}

Execute o Snippet para testar no FireFox


1

C ++, 628 bytes

Bem, isso não ficou muito curto:

#include <set>
#include <iostream>
using namespace std;struct R{string b,m;bool operator<(R r)const{return b<r.b;}};int w,h,t,j,k,z=1;char c,f;set<R> p,q;int m(R r,int x,int d,char a){for(j=x,c=r.b[x];(f=r.b[j+=d])==35;);if(c-68||f-46){r.b[x]=35;if(f-46)r.b[j-d]=c;r.m+=c;r.m+=44;r.m+=a;r.m+=10;if(c==68&j-d==t){cout<<r.m;z=0;}if(p.count(r)+q.count(r)==0){q.insert(r);}}}int main(){cin>>w>>c>>h>>c;R r;string l;for(;k++<h;){getline(cin,l);r.b+=l;}t=r.b.find(42);r.b[t]=35;q.insert(r);for(;z;){r=*q.begin();q.erase(q.begin());p.insert(r);for(k=0;z&&k<w*h;++k){if(r.b[k]>64){m(r,k,-1,76);m(r,k,1,82);m(r,k,-w,85);m(r,k,w,68);}}}}

Eu escolhi o C ++ porque queria usar as estruturas de dados ( set, string), mas é inerentemente bem detalhado. A solução tem um desempenho razoavelmente bom no desempenho, resolvendo o teste 2 em pouco mais de 2 segundos em um MacBook Pro, mesmo que não esteja otimizado para o tempo de execução.

Código antes de começar a eliminar espaços em branco e alguma outra redução de comprimento:

#include <set>
#include <iostream>

using namespace std;

struct R {
    string b, m;
    bool operator<(R r) const {return b < r.b; }
};

int w, h, t;
set<R> p, q;
bool z = true;

void m(R r, int k, int d, char a) {
    int j = k;
    char s = r.b[k], f;
    for (; (f = r.b[j += d]) == 35;);
    if (s - 68 || f - 46) {
        r.b[k] = 35;
        if (f - 46) {
            r.b[j - d] = s;
        }
        r.m += s;
        r.m += 44;
        r.m += a;
        r.m += 10;
        if (s == 68 && j - d == t) {
            cout << r.m;
            z = false;
        }
        if (p.count(r) + q.count(r) == 0) {
            q.insert(r);
        }
    }
}

int main() {
    char c;
    cin >> w >> c >> h >> c;
    string b, l;
    int k;
    for (k = 0; k < h; ++k) {
        getline(cin, l);
        b += l;
    }

    t = b.find(42);
    b[t] = 35;

    R r;
    r.b = b;
    q.insert(r);

    for ( ; z; ) {
        r = *q.begin();
        q.erase(q.begin());
        p.insert(r);

        for (k = 0; z && k < w * h; ++k) {
            c = r.b[k];
            if (c > 64) {
                m(r, k, -1, 76);
                m(r, k, 1, 82);
                m(r, k, -w, 85);
                m(r, k, w, 68);
            }
        }
    }

    return 0;
}

A idéia principal por trás do algoritmo é que dois conjuntos sejam mantidos:

  • q é o conjunto de configurações com processamento pendente.
  • p é o conjunto de configurações que foram processadas.

O algoritmo começa com a configuração inicial em q. A cada etapa, uma configuração é exibida qe adicionada a ptodos os movimentos possíveis de vedação, e as novas configurações resultantes são inseridas q.

Execução de teste:

bash-3.2$ ./a.out <test1
D,R
bash-3.2$ time ./a.out <test2
p,U
c,U
p,R
c,L
m,L
b,L
a,L
D,U
b,L
D,R
D,D
D,L

real    0m2.267s
user    0m2.262s
sys 0m0.003s
bash-3.2$ ./a.out <test3
v,U
D,L
bash-3.2$

Usando uma fila em vez de configurada para 'q', você pode encontrar soluções mais curtas em menos tempo (minha solução para o teste 2 são 6 etapas).
Edc65 16/05

@ edc65 Sim, fiquei inicialmente surpreso com o número de movimentos lá. Eu passei por isso para confirmar que é uma solução válida. Usar um FIFO para qcertamente seria melhor nesse sentido. O motivo pelo qual usei um conjunto foi porque eu queria evitar inserir a mesma entrada várias vezes. Mas comecei a ter dúvidas depois de ver o resultado.
Reto Koradi

Em termos de desempenho, você deve usar uma fila E um conjunto para evitar repetições. Mas coloque no conjunto uma versão modificada da grade, pois cada selo de bebê é intercambiável.
edc65 17/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.