Cow Camp
Análisis oficial (Python, Java)
Solución
Podemos restar de porque siempre obtenemos el caso de prueba de ejemplo y lo sumamos a nuestra respuesta final.
Sea el valor esperado después de a lo sumo envíos. Buscamos . Para obtener el valor esperado del envío final, podemos hallar la relación entre y .
Para esto podemos expresar en términos de y , que es la probabilidad de obtener exactamente casos de prueba.
Después de envíos, se espera obtener casos de prueba. Hay tres posibilidades si se decide reenviar.
Caso 1: Si se obtienen menos casos de prueba que , no se quiere reenviar.
Caso 2: Si se obtienen más casos de prueba que , sí se quiere reenviar.
Caso 3: Si se obtiene el mismo número de casos de prueba que , no importa si se reenvía. En este caso no perjudica reenviar o quedarse igual, así que podemos combinarlo con el Caso 1 (o el Caso 2).
La probabilidad de obtener a lo sumo casos de prueba es . Podemos multiplicar esto por para obtener el valor esperado de si nos detenemos antes del -ésimo envío y obtenemos
La probabilidad de obtener más de casos de prueba es . El valor esperado de obtener más de casos de prueba es la suma de desde hasta de la probabilidad de obtener casos de prueba multiplicada por , o .
Además, estas fórmulas para ambos casos pueden ayudarnos a demostrar matemáticamente que se puede fusionar el Caso 3 con el Caso 1 o el 2. La única diferencia entre ambos casos es si pertenece a la primera o a la segunda suma. Si lo fusionamos con el Caso 1, el término de la suma será . Si lo fusionamos con el Caso 3, también será . Esto ocurre porque solo se cumple cuando .
Sumar las dos partes da nuestra expresión final para el valor esperado:
Podemos resolver el problema simulando cada caso de prueba y recalculando las sumas para cada caso de prueba en . Sin embargo, esto se puede acelerar fácilmente a precalculando usando la identidad de Pascal y precalculando las sumas de prefijos tanto de como de para obtener los valores de las sumas en en lugar de . Esto nos da los primeros casos de prueba pero no es suficiente.
Para resolver el problema por completo, necesitamos quitar el factor . Podemos hacerlo si somos capaces de procesar varios valores de en un solo paso.
Afirmación: Podemos procesar varias consultas si los valores de y no cambian entre consultas.
Demostración: Sea y . Entonces nuestra fórmula queda
Como y son constantes, podemos extender esta fórmula y obtener
Simplificando obtenemos una sucesión geométrica.
Podemos reescribir del segundo al último término usando la fórmula de una serie geométrica.
Aquí hay más información sobre series geométricas .
Sin embargo, los valores de y no siempre permanecerán iguales. Podemos hacer búsqueda binaria sobre el número máximo de envíos que Bessie puede hacer antes de que cambien los valores de y . y cambiarán cuando cambie el valor de . Las condiciones de la búsqueda binaria se cumplen porque es una función no decreciente.
Como hay a lo sumo valores de , y pueden cambiar a lo sumo veces, así que la complejidad temporal resultante será .
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using db = double;
const int MAXT = 1000;
db prob[MAXT + 1][MAXT + 1];
db pref_prob[MAXT + 1];
db pref_exp[MAXT + 1];
// Función para saltar x envíos con constantes a y b y el valor
// esperado actual igual a E.
db skip_submissions(db a, db b, db E, ll x) {
return pow(a, x) * E + b * (pow(a, x) - 1) / (a - 1);
}
int main() {
int T;
ll K;
cin >> T >> K;
// Restamos el caso de prueba de ejemplo
T--;
// Precomputamos la probabilidad de elegir exactamente i casos de prueba usando
// la identidad de Pascal.
prob[0][0] = 1;
for (int i = 1; i <= T; i++) {
prob[i][0] = prob[i - 1][0] / 2;
for (int j = 1; j <= T; j++)
prob[i][j] = (prob[i - 1][j] + prob[i - 1][j - 1]) / 2;
}
// Creamos suma de prefijos de probabilidad
pref_prob[0] = prob[T][0];
for (int i = 1; i <= T; i++) pref_prob[i] = pref_prob[i - 1] + prob[T][i];
// Creamos suma de prefijos de valor esperado
pref_exp[0] = 0;
for (int i = 1; i <= T; i++) pref_exp[i] = pref_exp[i - 1] + prob[T][i] * i;
db E = 0;
while (K != 0) {
/*
* Hallamos el piso del valor esperado actual
* que actúa como el límite de cuántos envíos podemos saltar hacia adelante.
*/
ll cross = (ll)floor(E);
// Hallamos las constantes a y b
db a = pref_prob[cross], b = pref_exp[T] - pref_exp[cross];
// Búsqueda binaria para hallar cuántos envíos podemos saltar
ll lo = 1;
ll hi = 1e9;
while (lo < hi) {
ll mid = (lo + hi + 1) / 2;
// Revisamos si 'mid' envíos cruzan el límite 'cross'.
if (floor(skip_submissions(a, b, E, mid)) == cross) {
lo = mid;
} else {
hi = mid - 1;
}
}
/*
* Saltamos 'lo' envíos o la cantidad restante de envíos
* si quedan menos de 'lo' envíos.
*/
lo = min(lo, K);
E = skip_submissions(a, b, E, lo);
K -= lo;
}
cout << fixed << setprecision(20);
cout << E + 1 << endl;
}import math
def skip_submissions(a: float, b: float, e: float, x: float) -> float:
"""
Función que salta x envíos hacia adelante
con constantes a y b y valor esperado actual e
según la fórmula de la serie geométrica
"""
return pow(a, x) * e + b * (pow(a, x) - 1) / (a - 1)
t, k = map(int, input().split())
# Restamos el ejemplo
t -= 1
# Precomputamos la probabilidad de obtener i casos de prueba usando la identidad de Pascal
prob = [[0 for i in range(t + 1)] for j in range(t + 1)]
prob[0][0] = 1
for i in range(1, t + 1):
prob[i][0] = prob[i - 1][0] / 2
for j in range(1, t + 1):
prob[i][j] = (prob[i - 1][j] + prob[i - 1][j - 1]) / 2
# Sumas de prefijos de probabilidad
pref_prob = [0 for i in range(t + 1)]
pref_prob[0] = prob[t][0]
for i in range(1, t + 1):
pref_prob[i] = pref_prob[i - 1] + prob[t][i]
# Sumas de prefijos de valor esperado
pref_exp = [0 for i in range(t + 1)]
pref_exp[0] = 0
for i in range(1, t + 1):
pref_exp[i] = pref_exp[i - 1] + prob[t][i] * i
e = 0
while k != 0:
"""
Hallamos el piso del valor esperado actual
que también es el límite de cuántos envíos podemos saltar hacia adelante
"""
cross = math.floor(e)
# Hallamos a y b usando las sumas de prefijos precomputadas
a = pref_prob[cross]
b = pref_exp[t] - pref_exp[cross]
# Búsqueda binaria para ver cuántos envíos podemos saltar
lo = 1
hi = 10**9
while lo < hi:
mid = math.floor((lo + hi + 1) / 2)
if math.floor(skip_submissions(a, b, e, mid)) == cross:
lo = mid
else:
hi = mid - 1
"""
Saltamos lo envíos hacia adelante
Si el número restante de envíos es menor que lo,
saltamos los envíos restantes
"""
lo = min(lo, k)
e = skip_submissions(a, b, e, lo)
k -= lo
print(e + 1)