Dado un entero n
, calcule un conjunto de n
enteros únicos aleatorios en el rango 1..n^2
(inclusive) de modo que la suma del conjunto sea igual an^2
Aleatorio, en este caso, significa uniformemente aleatorio entre salidas válidas. Cada salida válida para un determinado n
debe tener una posibilidad uniforme de ser generada.
Por ejemplo, n=3
deben tener la oportunidad de dar salida a un tercio cada uno 6, 1, 2
, 3, 5, 1
o 4, 3, 2
. Como se trata de un conjunto, el orden es irrelevante, 4, 3, 2
es idéntico a3, 2, 4
Puntuación
El ganador es el programa que puede calcular el más alto n
en menos de 60 segundos.
Nota: para evitar una posible codificación parcial, todas las entradas deben tener menos de 4000 bytes
Pruebas
Todo el código se ejecutará en mi máquina local con Windows 10 (Razer Blade 15, 16GB RAM, Intel i7-8750H 6 núcleos, 4.1GHz, GTX 1060 en caso de que desee abusar de la GPU), así que proporcione instrucciones detalladas para ejecutar su código en mi maquina
A petición, las entradas se pueden ejecutar a través de Debian en WSL o en una máquina virtual Xubuntu (ambas en la misma máquina que la anterior)
Las presentaciones se realizarán 50 veces seguidas, el puntaje final será un promedio de los 50 resultados.
fuente
Respuestas:
Óxido , n ≈ 1400
Como correr
Construye con
cargo build --release
y corre contarget/release/arbitrary-randomness n
.Este programa se ejecuta más rápido con mucha memoria (siempre y cuando no se intercambie, por supuesto). Puede ajustar su uso de memoria editando la
MAX_BYTES
constante, actualmente establecida en 8 GiB.Cómo funciona
El conjunto se construye mediante una secuencia de decisiones binarias (cada número está dentro o fuera del conjunto), cada una de cuyas probabilidades se calcula combinatoriamente contando el número de conjuntos posibles que se pueden construir después de cada elección mediante programación dinámica.
El uso de memoria para n grande se reduce mediante el uso de una versión de esta estrategia de partición binomial .
Cargo.toml
src/main.rs
Pruébalo en línea!
(Nota: la versión TIO tiene algunas modificaciones. Primero, el límite de memoria se reduce a 1 GiB. Segundo, dado que TIO no le permite escribir ay
Cargo.toml
depender de cajas externas comorand
, en su lugar, ingresédrand48
de la biblioteca C usando el FFI. No me molesté en sembrarlo, por lo que la versión TIO producirá el mismo resultado en cada ejecución. No use la versión TIO para la evaluación comparativa oficial).fuente
ln_add_exp
verificando si la diferencia absoluta es mayor que ~ 15 más o menos, lo que puede ser más rápido si se agrega mucho.ln_add_exp
llamadas involucran entradas comparables.Java 7+, n = 50 en ~ 30 segundos en TIO
La versión sin golf de mi respuesta para la versión de código de golf de este desafío por ahora, con solo un cambio menor:
java.util.Random#nextInt(limit)
se usa en lugar de(int)(Math.random()*limit)
un número entero en el rango[0, n)
, ya que es aproximadamente el doble de rápido .Pruébalo en línea.
Explicación:
Enfoque utilizado:
El código se divide en dos partes:
n
cantidad de enteros aleatorios que sumann squared
.El paso 1 se realiza con los siguientes subpasos:
1) Genere una matriz de
n-1
cantidad de enteros aleatorios en el rango[0, n squared)
. Y agregue0
yn squared
a esta lista. Esto se hace enO(n+1)
rendimiento.2) Luego ordenará la matriz con la función incorporada
java.util.Arrays.sort(int[])
. Esto se hace enO(n*log(n))
rendimiento, como se indica en los documentos:3) Calcular la diferencia entre cada par. Esta lista resultante de diferencias contendrá
n
enteros que sumann squared
. Esto se hace enO(n)
rendimiento.Aquí un ejemplo:
Entonces, estos tres pasos anteriores son bastante buenos para el rendimiento, a diferencia del paso 2 y el ciclo alrededor de todo, que es una fuerza bruta básica. El paso 2 se divide en estos pasos secundarios:
1) La lista de diferencias ya está guardada en a
java.util.Set
. Verificará si el tamaño de este conjunto es igual an
. Si es así, significa que todos los valores aleatorios que generamos son únicos.2) Y también verificará que no contiene ningún
0
en el Conjunto, ya que el desafío solicita valores aleatorios en el rango[1, X]
, dondeX
esn squared
menos la suma de[1, ..., n-1]
, como lo indica @Skidsdev en el comentario a continuación.Si alguna de las dos opciones anteriores (no todos los valores son únicos o hay un cero presente), generará una nueva matriz y se establecerá nuevamente restableciendo el paso 1. Esto continúa hasta que tengamos un resultado. Debido a esto, el tiempo puede variar bastante. Lo he visto terminar en 3 segundos una vez en TIO durante
n=50
, pero también en 55 segundos una vezn=50
.Prueba de uniformidad:
No estoy completamente seguro de cómo demostrar que esto es completamente honesto. El
java.util.Random#nextInt
uniforme es seguro, como se describe en los documentos:Por supuesto, las diferencias entre estos valores aleatorios (ordenados) no son uniformes, pero los conjuntos en su conjunto son uniformes. Nuevamente, no estoy seguro de cómo probar esto matemáticamente, pero aquí hay un script que colocará los
10,000
conjuntos generados (paran=10
) en un Mapa con un contador , donde la mayoría de los conjuntos son únicos; algunos repiten dos veces; y la ocurrencia repetida máxima generalmente está en el rango[4,8]
.Instrucciones de instalación:
Dado que Java es un lenguaje bastante conocido con mucha información disponible sobre cómo crear y ejecutar código Java, lo mantendré breve.
Todas las herramientas utilizadas en mi código están disponibles en Java 7 (tal vez incluso en Java 5 o 6, pero usemos 7 por si acaso). Sin embargo, estoy bastante seguro de que Java 7 ya está archivado, por lo que sugeriría descargar Java 8 para ejecutar mi código.
Reflexiones sobre mejoras:
Me gustaría encontrar una mejora para la verificación de ceros y verificar que todos los valores son únicos. Podría comprobar
0
antes, asegurándome de que el valor aleatorio que agreguemos a la matriz no esté ya en él, pero significaría un par de cosas: la matriz debería ser unaArrayList
para que podamos usar el método incorporado.contains
; se debe agregar un ciclo while hasta que hayamos encontrado un valor aleatorio que aún no esté en la Lista. Dado que la comprobación de cero ahora se realiza con.contains(0)
el Set (que solo se verifica una vez), lo más probable es que el rendimiento lo verifique en ese punto, en comparación con agregar el ciclo con.contains
en la Lista, que se verificará al menosn
veces , pero muy probablemente más.En cuanto a la verificación de unicidad, solo tenemos nuestra
n
cantidad de enteros aleatorios que sumann squared
después del paso 1 del programa, por lo que solo entonces podemos verificar si todos son únicos o no. Puede ser posible mantener una Lista ordenable en lugar de una matriz, y verificar las diferencias entre ellas, pero dudo seriamente que mejore el rendimiento que solo ponerlas en unaSet
y verificar si el tamaño de ese Conjunto esn
una vez.fuente
n^2 - sum(1..n-1)
por ejemplo, paran=5
el número válido más grande es5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
n
, ¿verdad? En ese caso, puede agregar0
al conjunto y luego verificar que el tamaño sea (ahora) mayor quen
. Esto solo puede suceder si las diferencias son distintas de cero y distintas.HashSet.contains
en la mayoría de los casos está cercaO(1)
, y en el peor de los casos, estáO(n)
en Java 7 yO(log n)
en Java 8+ (se ha mejorado después de que hayan reemplazado el encadenamiento por la detección de colisión). Si se me permite devolver el Set con el agregado0
para la verificación, entonces es un poco mejor para el rendimiento, pero si tengo que llamarset.remove(0);
dentro del if, estoy bastante seguro de que el rendimiento es algo similar.Mathematica n = 11
fuente