Considere a seguinte pergunta da rodada 1C do Google Code Jam :
A Grande Muralha da China começa como uma linha infinita, onde a altura em todos os locais é .
Algum número de tribos , atacará a parede a parede de acordo com os seguintes parâmetros - um dia de início, , uma força de partida , uma coordenada oeste de início, e uma coordenada leste de início, . Este primeiro ataque ocorre no dia , no intervalo , a força . Se houver qualquer parte da Grande Muralha dentro de com altura , o ataque será bem-sucedido e, no final do dia, a parede será construída de modo que qualquer segmento dela dentro de de altura estaria então em alturaN ≤ 1,000 D S W E D [ W , E ] S [ W , E ] < S [ W , E ] < S S (ou maior, se algum outro ataque naquele dia atingir o mesmo segmento com força )
Cada tribo executará até ataques antes de recuar e cada ataque será determinado iterativamente do anterior. Toda tribo tem alguns , e que determinam sua sequência de ataques: eles esperarão dia entre os ataques, eles moverão seu alcance de ataque unidades para cada ataque (negativo = oeste, positivo = leste), embora o tamanho do alcance permaneça o mesmo e sua força também aumente / diminua em um valor constante após cada ataque.δ D δ X δ S δ D ≥ 1 δ X
O objetivo do problema é, dada uma descrição completa das tribos atacantes, determinar quantos de seus ataques serão bem-sucedidos.
Consegui codificar uma solução que funciona, executando em cerca de 20 segundos: acredito que a solução que implementei leva o tempo , em que o número total de ataques em uma simulação (máximo de ) e o número total de pontos de borda exclusivos nos intervalos de ataque (máximo de ).A = 1000000 X = 2000000
Em um nível alto, minha solução:
- Lê todas as informações da Tribo
- Calcula todas as coordenadas exclusivas para os intervalos de ataque -O ( A )
- Representa a parede como uma árvore binária atualizada preguiçosamente sobre os intervalos que rastreiam os valores mínimos de altura. Uma folha é a extensão de duas coordenadas sem nada no meio e todos os nós pais representam o intervalo contínuo coberto por seus filhos. -X O ( X log X )
- Gera todos os ataques que toda tribo executará e os classifica por dia -
- Para cada ataque, veja se seria bem-sucedido ( tempo de consulta). Quando o dia mudar, percorra todos os ataques bem-sucedidos não processados e atualize o mural de acordo ( hora de atualização para cada ataque). -log X O ( A log X )
Minha pergunta é a seguinte: Existe uma maneira de fazer melhor que ? Talvez haja alguma maneira estratégica de tirar proveito da natureza linear dos sucessivos ataques do Tribes? 20 segundos parecem muito longos para uma solução pretendida (embora o Java possa ser o culpado por isso).
fonte
Respostas:
O espaço óbvio para melhorias é este passo:
Sabemos que as tribos atacarão a partir de um dia específico, em intervalos regulares. Isso significa que devemos essencialmente mesclar várias listas pré-classificadas. Além disso, a declaração do problema nos diz que nunca haverá mais de 1.000 tribos (ou seja, 1.000 listas para mesclar); um pequeno número comparado aos 1.000.000 de ataques máximos! Dependendo dos tempos relativos da sua implementação, alternar isso pode reduzir o tempo de processamento pela metade.
Isso é realmente tudo o que posso sugerir para otimizar a complexidade teórica, mas não tenho provas de que seria ótimo após essa alteração.
Eu mesmo experimentei o quebra-cabeça, mas usei uma representação muito mais burra da parede: uma árvore de pesquisa binária (C ++,
std::map
para ser mais preciso) armazenando locais onde a altura da parede muda. Com isso, fui capaz de adicionar e remover nós, conforme necessário (ou seja, se uma seção complicada estivesse sujeita a um ataque grande e esmagador, ou vários ataques da mesma força tocados, o número de nós reduziria significativamente). Isso resolveu a grande entrada em 3,9 segundos (no meu laptop de desenvolvimento para especificações médias). Eu suspeito que existem várias razões para a melhoria:Não usei nenhum pré-processamento (os estágios são determinados acompanhando quando cada tribo atacará em seguida e procurando a menor articulação a cada turno). Novamente, isso é teoricamente pior, mas para a maioria dos casos, suspeito que a sobrecarga mais baixa signifique que foi mais rápido (testarei isso e retornarei a você).Atualização : eu adicionei uma fila de prioridade para ataques, semelhante ao método descrito acima (embora, em vez de criar a grande matriz, eu a tenha calculado em tempo real), vi o tempo diminuir para 3,0 segundos para a entrada grande.Resumindo, embora eu ache que seu algoritmo seja quase ideal no caso geral, existem várias maneiras de acelerar esse processo para entradas típicas .
fonte
O seguinte foi removido da pergunta, pois é uma resposta.
Examinar outras discussões e soluções bem-sucedidas parece indicar que a solução que descrevi é praticamente o algoritmo esperado. A desaceleração da minha solução possivelmente se deve apenas ao uso preguiçoso do boxe automático e de uma estrutura em árvore baseada em ponteiro, em vez de em uma matriz - então eu suspeito que, se existe uma solução, provavelmente não é um todo muito melhor do que o que está aqui.
A solução pode ser encontrada aqui . É muito parecido com o que eu publiquei aqui; então, estou muito mais inclinado a acreditar que não existe uma solução mais eficiente.
fonte