Necesito ayuda sobre este problema ACM ICPC. Mi idea actual es modelar esto como un problema de ruta más corta, que se describe en la declaración del problema.
Problema
Hay N = 1000
contenedores de desechos nucleares ubicados a lo largo de una línea de números 1-D en posiciones distintas de -500,000 to 500,000
, excepto x=0
. Una persona tiene la tarea de recoger todos los contenedores de basura. Cada segundo que no se recoge un contenedor de residuos, emite 1 unidad de radiación. La persona comienza a la unidad x = 0
y puede moverla 1
cada segundo, y la recolección de los desechos lleva un tiempo insignificante. Queremos encontrar la cantidad mínima de radiación liberada mientras recolectamos todos los contenedores.
Entrada de muestra:
4
Contenedores ubicados en [-12, -2, 3, 7]
.
El mejor orden para recolectar estos contenedores es [-2, 3, 7, -12]
, para un mínimo de emisión de 50
unidades. Explicación: la persona acude -2
en 2 segundos y durante ese tiempo 2 units
se emite radiación. Luego va a 3
(distancia:) 5
para que el cañón haya liberado 2 + 5 = 7
unidades de radiación. Tarda 4
más segundos en llegar a x = 7
donde ese barril ha emitido 2 + 5 + 4 = 11
unidades. Toma 19
segundos para llegar a x = -12
donde ese barril ha emitido 2 + 5 + 4 + 19 = 30
unidades. 2 + 7 + 11 + 30 = 50
, cual es la respuesta.
Notas
Hay una O(N!)
solución obvia . Sin embargo, he explorado métodos ambiciosos como pasar al más cercano o al clúster más cercano, pero no han funcionado.
He pensado en este problema durante bastante tiempo, y lo he modelado como un problema de búsqueda de gráficos:
- Lo insertamos
0
como una posición de referencia (Este será el estado inicial) - Luego, clasificamos las posiciones de menor a mayor.
- Luego hacemos un BFS / PFS, donde
state
consiste en- Dos enteros
l
yr
que representan un rango contiguo en la matriz de posición ordenada que ya hemos visitado - Un entero
loc
que nos dice si estamos en el punto final izquierdo o derecho del rango - Un número entero
time
que nos dice el tiempo transcurrido. - Un 'costo' entero que nos dice el costo total hasta ahora (según los nodos que hemos visitado)
- Dos enteros
- Desde cada estado podemos movernos a [l - 1, r] y [l, r + 1], ajustando los otros 3 enteros en consecuencia
- El estado final es [0, N], verificando ambas posiciones finales.
Sin embargo, parece que [L, R, loc]
no define de forma exclusiva un estado, y tenemos que almacenarlo L, R, loc, and time
, minimizando cost
en cada uno de estos. Esto lleva a un algoritmo exponencial, que todavía es demasiado lento para ser bueno.
¿Alguien puede ayudarme a ampliar mi idea o empujarla en la dirección correcta?
Editar: ¿ Quizás esto se pueda modelar como un problema de optimización de programación dinámica? Pensando en ello, tiene los mismos problemas que la solución de búsqueda de gráficos: solo porque la corriente cost
es baja no significa que sea la respuesta óptima para ese subproblema, ya que time
también afecta la respuesta en gran medida.
Codicioso no funciona: tengo un algoritmo de selección codicioso que estima el costo de mudarse a un lugar determinado (por ejemplo, si nos movemos a la derecha, duplicamos las distancias a los barriles izquierdos y tal).
¿Podría hacer una búsqueda de prioridad primero, con una heurística? La heurística podría combinar el costo del viaje actual con la cantidad de tiempo transcurrido.
fuente
Respuestas:
Creo que puede mejorar esto al ver el problema como un gráfico dirigido de pares de posiciones.
Para este ejemplo, usaré la línea con los valores -9, -6, -1, 3 y 5.
Debido a que es demasiado difícil dibujar un gráfico con solo texto, voy a representar los pares como una tabla. Podemos pensar que las celdas representan el estado donde se han recolectado todos los contenedores entre esas dos posiciones. Cada celda tiene dos valores, uno que representa el costo para ir a la izquierda y el otro que representa el costo para ir a la derecha (al siguiente contenedor).
Los valores pueden calcularse simplemente como la distancia entre los dos puntos multiplicada por el número de barriles fuera de esos dos puntos + 1 . Las celdas donde ambos números tienen el mismo signo representan casos en los que se han recolectado todos los barriles del signo opuesto. Estos se calculan utilizando solo el número de barriles en la dirección alejada de cero.
En la tabla, los valores de X significan que no puede ir en esa dirección (se han tomado todos los barriles en esa dirección). Las filas representan la posición actual del colector y las columnas representan la ubicación del próximo barril opuesto.
Las reglas para moverse entre cuadrados pueden ser algo confusas.
Para columnas numeradas negativas, las reglas son las siguientes:
Para columnas numeradas positivas, las reglas son las siguientes:
Ahora podemos ejecutar el algoritmo de Dijkstra para calcular la mejor ruta, usando estas reglas de movimiento para atravesar el gráfico. Nuestras posiciones iniciales son (-1, 3) y (3, -1) con costos iniciales de 5 y 15, respectivamente. Una vez que hemos calculado los valores para las dos posiciones finales (a la izquierda de (-9, -9) y a la derecha de (5, 5)), la más pequeña de las dos es nuestra respuesta.
El tiempo de ejecución para cada uno de los pasos son:
Los primeros dos pasos están dominados por el último, por lo que su tiempo de ejecución general es O (N ^ 2 log N), que debería ser un tiempo de ejecución lo suficientemente bueno para el desafío.
fuente
Distancia más corta
Ayer escribí una aplicación Java para resolver el problema. El problema es básicamente un problema de distancia más corta, como dijo SRJ en su comentario. La radiación solo muestra que puede acumular un valor junto con la distancia más corta.
Básicamente, esto es lo que hice.
Aquí hay algunos resultados de la aplicación
No optimicé el algoritmo de ninguna manera. Ni siquiera me di cuenta de que la lista de entrada de números estaba ordenada. (Soy un tonto)
Cuando ejecuté mi código con los valores máximos, 1,000 contenedores y un rango de -500,000 a 500,000, mi código tardó 3 segundos en ejecutarse. La mayor parte de ese tiempo fue escribir las 1,000 líneas de impresión en la consola.
No soy una gran persona O, pero creo que mi fuerza bruta que recorre el algoritmo de ruta más corta es O (N al cuadrado), no O (N!).
Si aproveché el hecho de que los números de entrada están ordenados, de modo que solo tuve que verificar los dos números a cada lado de donde estoy ubicado actualmente, podría obtener la aplicación cerca de O (N). Solo tengo que verificar 2 números porque estoy eliminando los elementos de la Lista a medida que los obtengo.
Usó diferentes algoritmos, como el algoritmo codicioso, para encontrar una solución aproximada.
Si mi programa hubiera tardado 3 horas en ejecutarse en lugar de 3 segundos, entonces tendría que tomar una decisión.
¿Es la solución suficientemente buena lo suficientemente buena?
En otras palabras, ¿estoy dispuesto a cambiar la velocidad de procesamiento por una respuesta lo suficientemente buena?
Si una respuesta lo suficientemente buena es lo suficientemente buena, entonces usa algoritmos de aproximación.
Si quieres la respuesta perfecta, tienes que recorrer todos los caminos más cortos. No hay ningún atajo.
Si alguien quiere que publique mi código, lo haré. Estoy seguro de que todavía hay errores, ya que quería ver si podía implementar un algoritmo de caminata más corto.
fuente
Tengo una solución que resolverá este problema a
2^N
tiempo, que es pobre, pero creo que es una forma útil de enmarcar el problema, así que pensé en publicar.En lugar de modelar el problema como un gráfico, modelaría que es un árbol de decisión binario (digamos
T
). En cada nivel tienes que elegir entre ir a la derecha o a la izquierda. Es bastante fácil calcular el "costo" de cada borde. Seah(K)
la altura del nodo actualK
, luego el costo del borde que va aleft_child(K) = h(K) x dist(K, left_child(K))
. Un cálculo similar es suficiente para el costo del borde para el niño correcto. Construye este árbol y realiza un seguimiento del costo acumulativo de los bordes hasta el final, e informa la ruta al nodo hoja con el costo total más pequeño.Tenga en cuenta que el cálculo del costo funciona porque la longitud de cada borde
dist(K, left_child(K))
representa el tiempo para ir al siguiente sitio, mientras que la altura del subárbol es el número de sitios restantes (por ejemplo, todavía emitiendo radiación).Ahora, el truco dentro de este marco es determinar si hay algunas heurísticas que puede usar para 'probar' que puede ignorar expandir la búsqueda a lo largo de alguna rama. Mi intuición es que para cualquier tipo de heurística existe una disposición de sitios que lo derrotará, pero quizás alguien pueda llegar a algo.
Varios han propuesto aplicar soluciones de ruta más corta para gráficos, tengo algunas dudas sobre si dicha solución puede funcionar. Sus vecinos en el 'gráfico' en el problema cambiarán dependiendo de la ruta que siga. Por ejemplo, en su publicación original con
[-12, -2, 3, 7]
si va a-2
entonces-12
y se3
convierte en 'vecinos' y si va a3
entonces-2
y7
son vecinos. Cada posible 'par' de valores positivos y negativos puede ser potencialmente vecino en el gráfico final. No conozco ningún algoritmo de ruta más corta que sea demostrablemente correcto en un gráfico dinámico.fuente
Creo que tiene más sentido pensar en cada etapa simplemente como una elección binaria entre ir directamente al barril más cercano e ir a la izquierda al barril más cercano. Simplemente tenga una función de costo que detalla el número de unidades de radiación en las que se incurriría en total al hacer cualquier movimiento y elija la que tenga el costo más bajo.
NO considere simplemente el barril más cercano, sino que suponga que al alejarse de un barril está agregando efectivamente el doble de radiación porque al alejarse también ha incurrido en el costo de tener que retroceder en esa dirección más tarde.
En su ejemplo de [-12, -2,3,7], moverse hacia la izquierda generaría un total de 14 (2 + 2 + 10) a la izquierda y 18 (2 + 2 + 5 + 9) a la derecha, para un total de 22. Mover a la derecha incurriría en 10 (3 + 3 + 4) a la derecha y 26 (3 + 3 + 5 + 15) a la derecha. Claramente a la izquierda es la solución correcta al principio. Se puede hacer un cálculo similar para cada movimiento sucesivo.
Después de eso, el problema básicamente se reduce a una búsqueda, por lo que la complejidad debería ser O (nlog (n)), que es MUCHO mejor que O (n!). Creo que esta es necesariamente la complejidad más baja que puede existir para este problema, ya que es básicamente un algoritmo de búsqueda basado en comparación para el que no es posible hacerlo mejor que O (nlog (n))
Aparentemente no estaba lo suficientemente claro con esta descripción, así que decidí hacerlo un poco más programático: 1. Calcule el costo incurrido yendo hacia la izquierda y el costo incurrido yendo hacia la derecha en función de la posición actual 2. Muévase la dirección menos costosa 3. Retire el barril alcanzado de la consideración al calcular el costo de moverse en una dirección
Cálculo del costo: 1. Identifique el barril más cercano en la dirección dada. Digamos que $ dist es la distancia desde la posición actual hasta el barril más cercano en la dirección dada. 2. El costo se inicializa como N * $ dist donde N solo considera barriles aún activos. 3. A esto, agregue la distancia que la nueva posición indicada por $ dist sería de cada barril restante.
fuente
[43, -18, -98, -82, 63]
[-10,-11, 10,20,30,40,50,60,70]
. La solución correcta y única es recolectar todos los negativos y luego recolectar los positivos. Para una respuesta de 455.Solución parcial: volveré a ello más tarde.
Suponga que la estrategia "predeterminada" se ejecuta completamente hacia la izquierda o hacia la derecha, lo que sea más barato. Ahora pregunte, ¿vale la pena hacer un pequeño viaje de ida para recoger un barril? Es bastante fácil calcular la respuesta.
Para su entrada de muestra, ejecutar todo a la derecha es más barato que a la izquierda. ¿Va a -2 vale la pena un viaje lateral? Reduce el costo de correr completamente hacia la derecha y luego vuelve a 0 en 14 (porque estaba "pagando" 4 unidades de radiación por movimiento de 0 a 3 en la estrategia predeterminada, ahora está en 3, estaba pagando 3 de 3 a 7, ahora es 2, etc.), además reduce en uno por movimiento su costo de mover de 0 a -2, ahorrando así 2 más para un total de 16.
Sin embargo, agrega un costo de ir a -2 y luego volver a 0 de 14 (4 unidades por movimiento a -2, 3 por movimiento a 0), para una ganancia neta de (16-14) = 2. Tenga en cuenta que para calcular esto no tiene que evaluar el costo exacto de resolver el problema completo para cada decisión: tiene suficiente información para decidir simplemente sabiendo si correr todo el camino hacia la izquierda es más barato que correr hacia la derecha, más cómo muchos contenedores de desechos están a cada lado de usted, y la distancia al 2 más cercano. Entonces eso es O (N ^ 2).
Excepto por un problema importante: he asumido que correrás hasta el final, y sabemos que podrías duplicarlo. Para limpiar eso, necesitamos actualizar mi cálculo. Para la entrada de muestra, supuse que ahorraría 14 al emitir 1 radiación total menos por unidad por segundo mientras se ejecuta de 0 a 7 y viceversa. Pero, si duplica antes de correr a 7, los ahorros se reducirán.
Eso es bastante malo, porque no sé cómo calcular la próxima doble vuelta sin probar todas las posibilidades, lo que nos vuelve a poner en O (2 ^ N).
Excepto: son 2 ^ N con poda. Calculé que el "viaje lateral" a -2 costaba 14, pero gané 16, si no tenía más viajes laterales antes de llegar al número más a la derecha. Si el número más a la derecha hubiera sido 5, sabría de inmediato que el viaje lateral a -2 no podría pagar. (Costo aún 14, beneficio máximo 12). Tampoco tengo que considerar ir a -2 y luego hacer un viaje lateral antes de llegar a 6, ya que eso siempre es inferior a ir directamente a ese punto en primer lugar.
fuente
Creo que puede resolverlo usando una búsqueda de amplitud, manteniendo no más de 2 * N ^ 2 tuplas de (boolean, int, int, int, string) donde las cadenas son tan largas como la ruta es complicada.
Las tuplas son (booleano mínimo o máximo, posición mínima viajada, posición máxima viajada, radiación total emitida, historial de trayectoria).
Veo el algoritmo así:
Encontrar y eliminar tuplas dominadas mejorará drásticamente el rendimiento. Puede valer la pena agregar una bandera 'ha criado "a cada tupla y dejar las tuplas criadas en el grupo.
También hay que tomar algunas decisiones importantes al decidir cómo almacenar las tuplas y buscar dominios y nuevos elementos para reproducirse.
fuente