¿Existe una solución más rápida para el problema Google Code Jam Great Wall?


16

Considere la siguiente pregunta de Google Code Jam ronda 1C :

La Gran Muralla de China comienza como una línea infinita, donde la altura en todos los lugares es 0 .

Algún número de tribus , atacarán el muro de acuerdo con los siguientes parámetros: un día de inicio, , una fuerza de inicio , una coordenada de inicio oeste, y una coordenada de inicio este, . Este primer ataque se produce en el día , en rango , en la fuerza . Si hay alguna porción de la Gran Muralla dentro de que tiene una altura , el ataque es exitoso, y al final del día, la pared se construirá de tal manera que cualquier segmento de ella dentro de de altura estaría entonces en alturaNN1000DSWED[W,E]S[W,E]<S[W,E]<SS (o mayor, si algún otro ataque ese día golpeó el mismo segmento con fuerza )S>S

Cada tribu realizará hasta ataques antes de retirarse, y cada ataque se determinará iterativamente a partir del anterior. Cada tribu tiene algunos , y δ S que determinan su secuencia de ataques: esperarán δ D1 días entre ataques, moverán sus unidades de rango de ataque para cada ataque (negativo = oeste, positivo = este), aunque el tamaño del rango seguirá siendo el mismo, y su fuerza también aumentará / disminuirá en un valor constante después de cada ataque.1000δreδXδSδre1δX

El objetivo del problema es, dada una descripción completa de las tribus atacantes, determinar cuántos de sus ataques serán exitosos.

Logré codificar una solución que funciona, ejecutándose en unos 20 segundos: creo que la solución que implementé toma tiempo , donde el número total de ataques en una simulación (máx. ) y el número total de puntos de borde únicos en los rangos de ataque (máx. ).O(UNIniciar sesiónUN+(UN+X)Iniciar sesiónX)UN=1000000X=2000000

En un nivel alto, mi solución:

  • Lee en toda la información de la tribu
  • Calcula todas las coordenadas únicas para los rangos de ataque -XO(UN)
  • Representa el Muro como un árbol binario perezosamente actualizado sobre larangos X que rastrea los valores mínimos de altura. Una hoja es el lapso de doscoordenadas X sin nada en el medio, y todos los nodos principales representan el intervalo continuo cubierto por sus hijos. - O ( X log X )XXO(XIniciar sesiónX)
  • Genera todos los ataques que realizará cada tribu, y los ordena por día - O(UNIniciar sesiónUN)
  • Para cada ataque, vea si sería exitoso ( tiempo de consulta). Cuando cambie el día, recorra todos los ataques exitosos no procesados ​​y actualice el muro en consecuencia ( registre el tiempo de actualización X para cada ataque). - O ( Un log X )Iniciar sesiónXIniciar sesiónXO(UNIniciar sesiónX)

Mi pregunta es esta: ¿hay alguna manera de hacerlo mejor que ? Quizás, ¿hay alguna forma estratégica de aprovechar la naturaleza lineal de los sucesivos ataques de Tribes? 20 segundos se sienten demasiado largos para una solución prevista (aunque Java podría ser el culpable de eso).O(UNIniciar sesiónUN+(UN+X)Iniciar sesiónX)


3
Por favor no lo cierres. Es una pregunta valida. Una respuesta sería una prueba de límite inferior, que muestra que no podemos hacerlo mejor, si de hecho es lo mejor que podemos hacer. Por ejemplo, supongo que podríamos usar el problema de distinción de elementos aquí, pero no hemos encontrado el tiempo para pensarlo.
Aryabhata

Lo mantendré abierto entonces :)
torquestomp

Respuestas:


2

El margen obvio de mejora es este paso:

Genera todos los ataques que realizará cada tribu, y los ordena por día - O(UNIniciar sesiónUN)

Sabemos que las tribus atacarán desde un día en particular, a intervalos regulares. Eso significa que esencialmente deberíamos fusionar muchas listas ordenadas previamente. Además, la declaración del problema nos dice que nunca habrá más de 1,000 tribus (es decir, 1,000 listas para fusionar); ¡un pequeño número en comparación con los 1,000,000 de ataques máximos! Dependiendo de los tiempos relativos de su implementación, cambiar esto podría reducir el tiempo de procesamiento a la mitad.

Eso es todo lo que puedo sugerir para optimizar la complejidad teórica, pero no tengo pruebas de que sería óptimo después de este cambio.


Le di una oportunidad al rompecabezas, pero utilicé una representación mucho más tonta de la pared: un árbol de búsqueda binario (C ++ std::mappara ser precisos) que almacena lugares donde la altura de la pared cambia. Con esto, pude agregar y eliminar nodos según fuera necesario (es decir, si una sección complicada estaba sujeta a un ataque grande y abrumador, o si se tocaban múltiples ataques de la misma fuerza, el número de nodos se reduciría significativamente). Esto resolvió la gran entrada en 3.9 segundos (en mi computadora portátil de desarrollo de especificaciones medias). Sospecho que hay varias razones para la mejora:

  • Como señaló, el boxeo y el desempaquetado pueden ser costosos, pero los contenedores basados ​​en plantillas de C ++ lo evitan por completo.
  • Si bien la representación de la pared que utilicé es teóricamente peor, en la gran mayoría de los casos, poder reducir dinámicamente el número de nodos lo hizo súper rápido (la mayoría de los casos de prueba alcanzaron el máximo en menos de 1k nodos, y todos menos 2 eran menos de 10k) . De hecho, el único caso que tomó un tiempo significativo fue el # 7, que parece haber estado probando muchos rangos no intersectantes.
  • No utilicé ningún preprocesamiento (las etapas se determinan haciendo un seguimiento de cuándo atacará cada tribu y buscando el punto más bajo en cada turno). Nuevamente, esto es teóricamente peor, pero para la mayoría de los casos sospecho que la sobrecarga más baja significa que fue más rápido (lo probaré y te responderé). Actualización : agregué una cola de prioridad para los ataques, similar al método descrito anteriormente (aunque en lugar de crear la matriz grande lo calculé sobre la marcha) y vi que el tiempo disminuía a 3.0 segundos para la entrada grande.

En resumen, aunque creo que su algoritmo es casi óptimo en el caso general, hay varias formas en que podría acelerarlo para las entradas típicas .


1

Lo siguiente fue eliminado de la pregunta, ya que es una respuesta.

Revisar otras discusiones y soluciones exitosas parece indicar que la solución que he descrito es más o menos el algoritmo esperado. La desaceleración en mi solución posiblemente se deba al uso lento del auto-boxeo y una estructura de árbol basada en punteros, en lugar de una matriz basada, así que sospecho que, si existe una solución, probablemente no sea un todo Mucho mejor que lo que hay aquí.

La solución se puede encontrar aquí . Es muy parecido a lo que he publicado aquí; así que estoy mucho más inclinado a creer que no existe una solución más eficiente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.