Pontos de visita em uma linha numérica, minimizando um custo não relacionado à distância


18

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 = 1000contê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 = 0e pode mover a 1unidade 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:

4Contentores 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 50unidades. Explicação: a pessoa entra -2em 2 segundos e durante esse tempo a 2 unitsradiação é emitida. Ele então vai para 3(distance 5:) para que o barril libere 2 + 5 = 7unidades de radiação. Ele leva 4mais alguns segundos para chegar x = 7onde o barril emitiu 2 + 5 + 4 = 11unidades. Ele leva 19alguns segundos para chegar x = -12onde o barril emitiu 2 + 5 + 4 + 19 = 30unidades. 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:

  1. Nós inserimos 0como uma posição de linha de base (este será o estado inicial)
  2. Depois, ordenamos as posições do menor para o maior.
  3. Em seguida, fazemos um BFS / PFS, no qual o stateconsiste
    • Dois números inteiros le rque representam um intervalo contíguo na matriz de posição classificada que já visitamos
    • Um número inteiro locque nos diz se estamos no ponto final esquerdo ou direito do intervalo
    • Um número inteiro timeque nos diz o tempo decorrido
    • Um "custo" inteiro que nos informa o custo total até o momento (com base nos nós que visitamos)
  4. De cada estado, podemos mover para [l - 1, r] e [l, r + 1], ajustando os outros 3 números inteiros de acordo
  5. 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 costcada 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 costestá baixa não significa que é a resposta ideal para esse subproblema, pois timetambé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.


Que tal algoritmos de caminho mais curto? Como o algoritmo de Dijkstra?
suraj_fale

Eu tentei isso, mas acho que estou fazendo algo realmente errado. Descrevi meu algoritmo (que era a primeira pesquisa prioritária ou BFS) na parte inferior, com a lista numerada.
Barron W. #


Desculpe, não vejo como esses dois problemas estão relacionados. Você pode explicar?
Barron W. #

2
Este é um problema de prática do ACM ICPC, não um problema da vida real. Em outra nota, tentei reduzir o estado, mas sem sucesso. Tentei escrever uma solução DP, mas isso também não funcionou. O estado era L, R, POS.
Barron W.

Respostas:


4

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.

    +------+------+------+------+------+
    |  -9  |  -6  |  -1  |   3  |   5  |
+---+------+------+------+------+------+
|-9 |      |      |      | X, 24| X, 14|
+---+------+------+------+------+------+
|-6 | 3, X |      |      | 9, 27| 6, 22|
+---+------+------+------+------+------+
|-1 |      |10, X |      |20, 8 |15, 18|
+---+------+------+------+------+------+
| 3 |24, 4 |27, 6 |16, 8 |      | X, 2 |
+---+------+------+------+------+------+
| 5 |14, X |22, X |18, X |      |      |
+---+------+------+------+------+------+

As regras para mover-se entre quadrados podem ser um pouco confusas.

Para colunas numeradas negativas, as regras são as seguintes:

  • Ir para a direita desce uma célula.
  • Ir para a esquerda desce uma célula e espelha na diagonal.
  • Se o valor certo for X, ir para a esquerda move-se para a diagonal (onde a coluna e a linha são iguais) e deixa um por uma.

Para colunas numeradas positivas, as regras são as seguintes:

  • Ir para a esquerda move uma célula para cima.
  • Ir para a direita sobe uma célula e espelha na diagonal.
  • Se o valor esquerdo for X, ir para a direita desce para a diagonal e para a direita em um.

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 é:

  • O (N log N) para classificar inicialmente os valores de entrada ao longo da linha
  • O (N ^ 2) para o cálculo da tabela / gráfico
  • O (N ^ 2 log N) para executar Dijkstra's no gráfico (Nota: existem no máximo duas arestas para qualquer vértice).

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.


1

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.

  • Coloque os números dos contêineres em uma lista <Integer>
  • Enquanto a lista contém elementos;
    • Encontre os elementos mais próximos
    • Se houver um elemento, caminhe até lá e remova o elemento.
    • Se houver dois elementos, copie o caminho e vá para os dois elementos
  • Encontre o caminho com o menor valor de radiação.

Aqui estão algumas saídas do aplicativo

10 containers are located at [-90, -75, -47, -9, 9, 26, 48, 50, 64, 79].

You walk to position -9 and pick up the container.  The total radiation emitted is 90 units.
You walk to position 9 and pick up the container.  The total radiation emitted is 252 units.
You walk to position 26 and pick up the container.  The total radiation emitted is 388 units.
You walk to position 48 and pick up the container.  The total radiation emitted is 542 units.
You walk to position 50 and pick up the container.  The total radiation emitted is 554 units.
You walk to position 64 and pick up the container.  The total radiation emitted is 624 units.
You walk to position 79 and pick up the container.  The total radiation emitted is 684 units.
You walk to position -47 and pick up the container.  The total radiation emitted is 1,062 units.
You walk to position -75 and pick up the container.  The total radiation emitted is 1,118 units.
You walk to position -90 and pick up the container.  The total radiation emitted is 1,133 units.

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.


1

Eu tenho uma solução que resolverá esse problema a 2^Ntempo, 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. Seja h(K)a altura do nó atual e K, em seguida, o custo da aresta left_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 -2então -12e 3se tornar 'vizinho' e se for para 3então -2e 7for 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.


0

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.


1
Isso nem sempre funciona. Talvez você possa classificar as cordas e fazer uma pesquisa em que o estado contenha um intervalo [i..j] (que significa qual intervalo você visitou) e o custo e o horário atual.
Barron W.

Quando isso não funciona?
Slater Victoroff

Houve um caso de teste simples em que isso falhou. Vou tentar encontrá-lo, mas foi com N = 4 ou 5.
Barron W.

[43, -18, -98, -82, 63]
precisa

Também casos como [-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.
Barron W.

0

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.


0

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:

  1. Inicialize o conjunto de tuplas em uma única entrada (min, 0, 0, 0, "")
  2. Encontre um elemento na piscina que tenha o mínimo de radiação emitida. Se o mínimo e o máximo correspondem ao mínimo e máximo de todos os barris, o histórico do caminho é a solução ideal. Caso contrário, exclua-o da piscina.
  3. Calcule os 2 descendentes dessa tupla, cada um dos quais corresponde a caminhar para a esquerda ou direita para o próximo barril não processado.
  4. Insira os descendentes na piscina. Se já houver um elemento no pool com o mesmo booleano, min e max que um novo descendente, descarte o elemento com a contagem de radiação mais alta.
  5. ir para 2.

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.

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.