Preciso de ajuda para este problema do ACM ICPC. Minha idéia atual é modelar isso como um problema de caminho mais curto, descrito na declaração do problema.
Problema
Existem N = 1000
contêineres de resíduos nucleares localizados ao longo de uma linha numérica 1-D em posições distintas-500,000 to 500,000
, exceto x=0
. Uma pessoa é encarregada de coletar todos os caixotes do lixo. Cada segundo que um recipiente de lixo não é coletado, emite 1 unidade de radiação. A pessoa começa x = 0
e pode mover a 1
unidade a cada segundo, e a coleta de resíduos leva um tempo insignificante. Queremos encontrar a quantidade mínima de radiação liberada ao coletar todos os recipientes.
Entrada de amostra:
4
Contentores localizados em [-12, -2, 3, 7]
.
A melhor ordem para coletar esses contêineres é [-2, 3, 7, -12]
, para uma emissão mínima de 50
unidades. Explicação: a pessoa entra -2
em 2 segundos e durante esse tempo a 2 units
radiação é emitida. Ele então vai para 3
(distance 5
:) para que o barril libere 2 + 5 = 7
unidades de radiação. Ele leva 4
mais alguns segundos para chegar x = 7
onde o barril emitiu 2 + 5 + 4 = 11
unidades. Ele leva 19
alguns segundos para chegar x = -12
onde o barril emitiu 2 + 5 + 4 + 19 = 30
unidades. 2 + 7 + 11 + 30 = 50
, qual é a resposta.
Notas
Existe uma O(N!)
solução óbvia . No entanto, eu explorei métodos gananciosos, como mover para o mais próximo ou para o cluster mais próximo, mas eles não funcionaram.
Pensei nesse problema por um bom tempo e o modelei como um problema de pesquisa de gráfico:
- Nós inserimos
0
como uma posição de linha de base (este será o estado inicial) - Depois, ordenamos as posições do menor para o maior.
- Em seguida, fazemos um BFS / PFS, no qual o
state
consiste- Dois números inteiros
l
er
que representam um intervalo contíguo na matriz de posição classificada que já visitamos - Um número inteiro
loc
que nos diz se estamos no ponto final esquerdo ou direito do intervalo - Um número inteiro
time
que nos diz o tempo decorrido - Um "custo" inteiro que nos informa o custo total até o momento (com base nos nós que visitamos)
- Dois números inteiros
- De cada estado, podemos mover para [l - 1, r] e [l, r + 1], ajustando os outros 3 números inteiros de acordo
- O estado final é [0, N], verificando as duas posições finais.
No entanto, parece que [L, R, loc]
não define um estado exclusivamente, e precisamos armazenar L, R, loc, and time
, minimizando cost
cada um deles. Isso leva a um algoritmo exponencial, que ainda é muito lento para o bem.
Alguém pode me ajudar a expandir minha ideia ou empurrá-la na direção certa?
Edit: Talvez isso possa ser modelado como um problema de otimização de programação dinâmica? Pensando nisso, ele tem os mesmos problemas da solução de pesquisa de gráficos - apenas porque a corrente cost
está baixa não significa que é a resposta ideal para esse subproblema, pois time
também afeta muito a resposta.
O ganancioso não funciona: eu tenho um algoritmo de seleção ganancioso que estima o custo da mudança para um determinado local (por exemplo, se movermos para a direita, dobraremos as distâncias para os barris da esquerda e assim por diante).
Você poderia fazer uma pesquisa com prioridade, com uma heurística? A heurística pode combinar o custo da viagem atual com a quantidade de tempo decorrido.
fonte
Respostas:
Eu acho que você pode melhorar isso vendo o problema como um gráfico direcionado de pares de posições.
Neste exemplo, usarei a linha com os valores -9, -6, -1, 3 e 5.
Como é muito difícil desenhar um gráfico apenas com texto, vou representar os pares como uma tabela. Podemos pensar nas células como representando o estado em que todos os contêineres entre essas duas posições foram coletados. Cada célula possui dois valores, um representando o custo para ir para a esquerda e o outro representando o custo para ir para a direita (para o próximo contêiner).
Os valores podem ser simplesmente calculados como a distância entre os dois pontos multiplicada pelo número de barris fora desses dois pontos + 1 . As células em que os dois números têm o mesmo sinal representam casos em que todos os barris do sinal oposto foram coletados. Estes são calculados usando apenas o número de barris na direção longe de zero.
Na tabela, os valores de X significam que você não pode ir nessa direção (todos os barris nessa direção foram levados). As linhas representam a posição atual do coletor e as colunas representam a localização do próximo barril oposto.
As regras para mover-se entre quadrados podem ser um pouco confusas.
Para colunas numeradas negativas, as regras são as seguintes:
Para colunas numeradas positivas, as regras são as seguintes:
Agora podemos executar o algoritmo de Dijkstra para calcular o melhor caminho, usando essas regras de movimento para percorrer o gráfico. Nossas posições iniciais são (-1, 3) e (3, -1) com custos iniciais de 5 e 15, respectivamente. Uma vez calculados os valores para as duas posições finais (esquerda de (-9, -9) e direita de (5, 5)), o menor dos dois é a nossa resposta.
O tempo de execução de cada uma das etapas é:
As duas primeiras etapas são dominadas pela última, portanto, seu tempo de execução geral é O (N ^ 2 log N), que deve ser um tempo de execução suficientemente bom para o desafio.
fonte
Menor distância
Ontem escrevi um aplicativo Java para resolver o problema. O problema é basicamente um problema de menor distância, como o SRJ disse em seu comentário. A radiação apenas mostra que você pode acumular um valor junto com a menor distância.
Basicamente, aqui está o que eu fiz.
Aqui estão algumas saídas do aplicativo
Não otimizei o algoritmo de forma alguma. Eu nem percebi que a lista de números de entrada estava classificada. (Eu sou um idiota.)
Quando executei meu código com os valores máximos, 1.000 contêineres e um intervalo de -500.000 a 500.000, meu código levou 3 segundos para ser executado. Na maior parte do tempo, escrevia as 1.000 linhas de impressão no console.
Eu não sou uma grande pessoa O, mas acho que minha força bruta percorrendo o algoritmo de caminho mais curto é O (N ao quadrado), não O (N!).
Se eu aproveitasse o fato de os números de entrada serem classificados, de modo que eu só precisei verificar os dois números em ambos os lados de onde estou atualmente, poderia baixar o aplicativo perto de O (N). Eu só tenho que verificar 2 números, porque estou removendo os elementos da Lista conforme chego a eles.
Você usou algoritmos diferentes, como o algoritmo ganancioso, para encontrar uma solução aproximada.
Se meu programa tivesse levado três horas para ser executado em vez de três segundos, você teria uma opção a fazer.
A solução é boa o suficiente?
Em outras palavras, estou disposto a trocar a velocidade de processamento por uma resposta suficientemente boa?
Se uma resposta boa o suficiente, você usa algoritmos de aproximação.
Se você quer a resposta perfeita, precisa percorrer todos os caminhos mais curtos. Não há atalho.
Se alguém quiser que eu publique meu código, eu irei. Tenho certeza de que ainda existem bugs, pois queria ver se poderia implementar um algoritmo de caminhada mais curto.
fonte
Eu tenho uma solução que resolverá esse problema a
2^N
tempo, o que é ruim, mas acho que é uma maneira útil de enquadrar o problema, então pensei em publicar.Em vez de modelar o problema como um gráfico, eu modelaria que é uma árvore de decisão binária (digamos
T
). Em cada nível você tem que escolher entre ir para a direita ou para a esquerda. É bastante fácil calcular o 'custo' de cada borda. Sejah(K)
a altura do nó atual eK
, em seguida, o custo da arestaleft_child(K) = h(K) x dist(K, left_child(K))
. Um cálculo semelhante é suficiente para o custo da borda para o filho certo. Você constrói essa árvore e monitora o custo cumulativo das arestas até o fim e relata o caminho para o nó da folha com o menor custo total.Observe que o cálculo de custo funciona porque o comprimento de cada aresta
dist(K, left_child(K))
representa o tempo para o próximo site, enquanto a altura da subárvore é o número de sites restantes (por exemplo, ainda emitindo radiação).Agora, o truque dessa estrutura é determinar se existem algumas heurísticas que você pode usar para 'provar' que você pode ignorar a expansão da pesquisa em algum ramo. Minha intuição é que, para qualquer heurística, existe um arranjo de sites que a derrotará, mas talvez alguém possa inventar alguma coisa.
Vários propuseram a aplicação de soluções de caminho mais curto para gráficos, tenho algumas dúvidas sobre se essa solução pode funcionar. Seus vizinhos no 'gráfico' do problema mudarão dependendo do caminho que você seguir. Por exemplo, em sua postagem original,
[-12, -2, 3, 7]
se você for para-2
então-12
e3
se tornar 'vizinho' e se for para3
então-2
e7
for vizinho. Todo 'par' possível de valores positivos e negativos pode ser potencialmente zero no gráfico final. Não conheço nenhum algoritmo de caminho mais curto comprovadamente correto em um gráfico dinâmico.fonte
Eu acho que faz mais sentido pensar em cada estágio simplesmente como uma escolha binária entre ir direto para o barril mais próximo e ir para a esquerda para o barril mais próximo. Basta ter uma função de custo que detalha o número de unidades de radiação que seriam incorridas no total, fazendo qualquer movimento e escolha a que tiver o menor custo.
NÃO considere simplesmente o barril mais próximo, mas assuma que, ao se afastar de um barril, você está efetivamente adicionando o dobro da radiação, porque ao se afastar, você também incorre no custo de ter que voltar mais tarde nessa direção.
No seu exemplo de [-12, -2,3,7], mover para a esquerda incorreria em um total de 14 (2 + 2 + 10) à esquerda e 18 (2 + 2 + 5 + 9) à direita, para um total de 22. Mover para a direita incorreria em 10 (3 + 3 + 4) à direita e 26 (3 + 3 + 5 + 15) à direita. Claramente esquerda é a solução certa em primeiro lugar. Um cálculo semelhante pode ser feito para cada movimento sucessivo.
Depois disso, o problema basicamente se reduz a uma pesquisa, portanto a complexidade deve ser O (nlog (n)), que é MUITO melhor que O (n!). Acredito que essa seja necessariamente a menor complexidade possível para esse problema, já que é basicamente um algoritmo de pesquisa baseado em comparação para o qual não é possível fazer melhor que O (nlog (n))
Aparentemente, eu não estava claro o suficiente com esta descrição, então decidi torná-la um pouco mais programática: 1. Calcule o custo incorrido indo à esquerda e o custo incorrido indo à direita com base na posição atual 2. Mova-se a direção menos dispendiosa 3. Remova o barril atingido da consideração no cálculo do custo de se mover em uma direção
Cálculo do custo: 1. Identifique o barril mais próximo na direção especificada. Digamos que $ dist seja a distância da posição atual até o barril mais próximo na direção especificada. 2. O custo é inicializado como N * $ dist, onde N considera apenas barris ainda ativos. 3. Para isso, adicione a distância que a nova posição indicada por $ dist estaria de cada barril restante.
fonte
[43, -18, -98, -82, 63]
[-10,-11, 10,20,30,40,50,60,70]
. A solução correta e única é coletar todos os negativos e depois coletar os positivos. Para uma resposta de 455.Solução parcial - voltarei a ele mais tarde.
Suponha que a estratégia "padrão" seja executada totalmente à esquerda ou à direita, o que for mais barato. Agora pergunte, vale a pena fazer uma pequena viagem para o outro lado para pegar um barril. É bastante fácil calcular a resposta.
Para você obter uma amostra da entrada, executar todo o caminho certo é mais barato do que todo o caminho restante. Vai -2 vale uma viagem de lado? Reduz o custo de rodar todo o caminho certo e depois volta para 0 em 14 (porque você estava "pagando" 4 unidades de radiação por movimento de 0 a 3 na estratégia padrão, agora é baixo para 3, você pagava 3 de 3 para 7, agora são 2, etc), mais reduz em um por movimento o custo de passar de 0 para -2, economizando mais 2 para um total de 16.
No entanto, ele adiciona um custo de ir para -2 e voltar para 0 de 14 (4 unidades por movimento para -2, 3 por movimento de volta para 0), para um ganho líquido de (16-14) = 2. Observe que, para calcular isso, você não precisa avaliar o custo exato da solução de todo o problema para cada decisão - você tem informações suficientes para decidir apenas sabendo se correr até o fim é mais barato do que correr até o fim, além de como muitos contêineres de lixo estão em cada lado de você e a distância até o 2. mais próximo. Portanto, é O (N ^ 2).
Exceto por um problema importante - presumi que você vá até o fim e sabemos que você poderá voltar atrás. Para limpar isso, precisamos atualizar meu cálculo. Para a entrada de amostra, presumi que você economizaria 14 ao emitir 1 a menos de radiação total por unidade por segundo, executando de 0 a 7 e vice-versa. Mas, se você voltar antes da corrida para 7, a economia será reduzida.
Isso é muito ruim, porque eu não sei como calcular o próximo double-back sem tentar todas as possibilidades, o que nos leva de volta a O (2 ^ N).
Exceto - é 2 ^ N com poda. Calculei que a "viagem lateral" para -2 custa 14, mas ganhei 16, se eu não tivesse mais viagens antes de chegar ao número mais à direita. Se o número mais à direita tivesse sido 5, eu saberia imediatamente que a viagem para -2 não poderia valer a pena. (Custo ainda 14, benefício máximo 12). Também não tenho que considerar ir para -2 e fazer uma viagem lateral antes de chegar a 6, pois isso é sempre inferior a ir direto para esse ponto em primeiro lugar.
fonte
Eu acho que você pode resolvê-lo usando uma pesquisa abrangente, mantendo não mais do que 2 * N ^ 2 tuplas de (booleano, int, int, int, string) em que as seqüências de caracteres têm o comprimento do caminho complicado.
As tuplas são (mínimo ou máximo booleano, posição mínima percorrida, posição máxima percorrida, radiação total emitida, histórico do caminho).
Eu vejo o algoritmo indo assim:
Encontrar e remover tuplas dominadas melhorará drasticamente o desempenho. Pode valer a pena adicionar um sinalizador 'criou' a cada tupla e deixar tuplas criadas no pool.
Também há algumas decisões importantes a serem tomadas ao decidir como armazenar as tuplas e procurá-las por domínios e novos elementos a serem reproduzidos.
fonte