Solución a “Números Libres”

Concurso: Preselectivo para la IOI 2015, Etapa 1, Problemset 1
Autor: Freddy Román Cepeda
Fuente: Edgar Augusto Santiago Nieves

Para resolver este problema hacía falta tener en mente la definición de square-free. Como tanto a y b no son divisibles por el cuadrado de un primo, la única manera de que su producto deje de ser square-free sería que ambos compartieran un factor primo.

Para obtener todos los puntos de la primer subtarea bastaba con computar a \times b e iterar sobre todos los números menores a ese producto y revisar si es divisible por el cuadrado de alguno de ellos.

Para obtener los puntos de la segunda subtarea era suficiente iterar hasta \max(\sqrt{a}, \sqrt{b}), para factorizar a a y b.

Por último, para obtener el resto de los puntos bastaba notar que si a y b comparten un factor primo, entonces su máximo común divisor es distinto de 1.

El siguiente código implementa esta solución:

Solución a “Suma Manhattan”

Concurso: Preselectivo para la IOI 2015, Etapa 1, Problemset 1
Autor: Freddy Román Cepeda
Fuente: Freddy Román Cepeda

Este problema requería manipular con cuidado la expresión que había que computar.
Recordemos que nos piden computar

\sum_{0 \leq i < j < N} manhattan(S_i,S_j).

Para resolver la primer subtarea bastaba con iterar sobre todas las parejas de puntos y calcular su distancia. Esto corre en tiempo cuadrático y no es suficiente para obtener todos los puntos.

La siguiente subtarea era una pista: se puede computar la distancia Manhattan de dos puntos considerando por separado sus coordenadas en x y y. Ahora nos preocuparemos por calcular la siguiente expresión:

\sum_{0 \leq i < j < N} |a_i - a_j|.

Donde a son las coordenadas en x o y. El problema está en el valor absoluto. La manera más sencilla de deshacernos de él es ordenar la secuencia a, de tal manera que a_i \leq a_j. Entonces tenemos:

\sum_{0 \leq i < j < N} |a_i - a_j| = \sum_{0 \leq i < j < N} |a_j - a_i| = \sum_{0 \leq i < j < N} a_j - a_i.

La primer igualdad es verdadera porque |x| = |-x| para cualquier x. La segunda es porque como ahora a está ordenado, como a_j \geq a_i \implies a_j - a_i \geq 0, el valor absoluto no hace nada.

Podemos entonces separar la suma en dos términos:

\sum_{0 \leq i < j < N} a_j - \sum_{0 \leq i < j < N} a_i.

Analicemos el primer término. Estamos sumando sobre todas las j tantas veces haya una i menor que ella. Eso quiere decir que cada a_j la vamos a sumar j veces (nota que a_0 la sumamos 0 veces).

El segundo término nos dice que sumaremos todas las a_i tantas veces haya una j mayor a ella. Eso quiere decir que cada a_i la vamos a sumar N-i-1 veces (nota que a_{N-1} la sumamos 0 veces).

Juntando esas ideas, entonces tenemos:

\sum_{j = 0}^{N-1} j \cdot a_j - \sum_{i = 0}^{N-1} (N - i - 1) \cdot a_i

= \sum_{i = 0}^{N-1} i \cdot a_i - \sum_{i = 0}^{N-1} (N - i - 1) \cdot a_i.

= \sum_{i = 0}^{N-1} (i - (N - i - 1)) \cdot a_i.

= \sum_{i = 0}^{N-1} (2i - N + 1) \cdot a_i.

Y con eso terminamos: ahora tenemos una expresión que podemos computar fácilmente en tiempo lineal. Hay que tener cuidado al computar esto: La primera observación es que hay que estar tomando módulo después de cada operación porque en cualquier momento puede haber un overflow. Algunos competidores obtuvieron 60 puntos en este problema por no tomar esto en cuenta. La segunda observación es que el término (2i - N + 1) \cdot a_i no necesariamente cabe en un entero signado de 32 bits — hacía falta utilizar enteros de 64 bits para realizar este cálculo.

Aquí está mi código que implementa la solución anterior.

El nuevo ranking de omegaUp

Con este commit hemos introducido un cambio significativo en la forma de calcular el ranking general de omegaUp. Ahora no sólo es importante cuántos problemas ha resuelto un usuario sino que también estamos incluyendo la dificultad de cada uno de esos problemas. La dificultad es inversamente proporcional a la cantidad de soluciones completas (AC) que tiene ese problema.

Para ser más precisos, estamos definiendo los puntos que da un problema así: P_i = \frac{100}{log_2(N+1)}, donde N representa la cantidad de ACs que tiene un problema. Entre más ACs tenga un problema, menos puntos va a valer. Sólo estamos contando a lo más 1 AC por usuario para evitar que las soluciones de una misma persona afecten artificialmente los puntos de score del problema.

El score que usamos en el ranking esta dado por la suma de los puntos de todos los problemas que un usuario ha resuelto con AC.

Este nuevo sistema implica que los scores ahora son dinámicos: con cada problema que un usuario resuelva se va a afectar el score de otras personas que ya hayan resuelto el mismo problema anteriormente. La única manera de subir el score es resolviendo más problemas ya que, con el tiempo, el valor que otorga cada problema va a decaer.

También agregamos una columna en la lista de problemas disponibles, Puntos para ranking, que ayuda a los usuarios a saber qué problemas atacar y cuánto valen en este nuevo sistema. Es importante notar que el valor que se muestra es el actual: en el momento que resuelvas un problema, los puntos que otorga van a cambiar.

Qué te parece el nuevo sistema? Subiste o bajaste en el ranking? Déjanos tus dudas y sugerencias en los comentarios.