Skip to Content

Race

Solución en video

Por Jonathan Paulson

Video de YouTube (4KcvKIJP9KY)

Código de la solución en video
#include <cassert> #include <cstdio> #include <iostream> #include <vector> // sum(n) = 1 + 2 + ... + n = n * (n + 1) / 2 long long sum(int n) { return 1LL * n * (n + 1) / 2; } // a + (a + 1) + (a + 2) + ... + b = \sum_{i = a}^b i long long sum(int a, int b) { return a <= b ? sum(b) - sum(a - 1) : 0; } // Dado que empezamos a velocidad s, ¿podemos bajar a velocidad x en a lo sumo l // metros? ¿Cuántos metros toma parar si la velocidad actual es s y // el objetivo es x? (s - 1) + (s - 2) + ... + x metros. Se nos permite pasarnos, así que // mientras tengamos (s - 1) + (s - 2) + ... + (x + 1) + 1 metros, estamos bien. bool ok(int speed, int x, int l) { if (speed <= x) { return true; } long long need = sum(x + 1, speed - 1) + 1; return (need <= l); } int main() { freopen("race.in", "r", stdin); freopen("race.out", "w", stdout); int k, n; std::cin >> k >> n; std::vector<int> arr(n); for (int &x : arr) { std::cin >> x; } for (int i = 0; i < n; i++) { // Voraz. Vamos lo más rápido que podamos pero asegurándonos de poder parar a tiempo. int x = arr[i]; int speed = 0; int left = k; int res = 0; while (left > 0) { if (ok(speed + 1, x, left - (speed + 1))) { // aceleramos si podemos speed++; } else if (ok(speed, x, left - speed)) { // si no, mantenemos la misma velocidad si podemos } else if (ok(speed - 1, x, left - (speed - 1))) { // frenamos si tenemos que // comprobamos que nos queda espacio suficiente para parar a tiempo. speed--; } left -= speed; res++; } std::cout << res << '\n'; } }
import java.io.*; import java.util.*; public class race { // S(n) = 1+2+...+n = n*(n+1)/2 static long S(long n) { return n * (n + 1) / 2; } // A+(A+1)+(A+2)+...+B = \sum_{i=A}^B i static long sum(long A, long B) { return A <= B ? S(B) - S(A - 1) : 0; } // Dado que empezamos a velocidad S, ¿podemos bajar a velocidad X en a lo sumo L // metros? ¿Cuántos metros toma parar si la velocidad actual es S y // el objetivo es X? (S-1) + (S-2) + ... + X metros. Se nos permite pasarnos, así // que mientras tengamos (S-1)+(S-2)+...+(X+1)+1 m estamos bien. static boolean ok(long S, long X, long L) { if (S <= X) { return true; } long need = sum(X + 1, S - 1) + 1; return need <= L; } static long solve(long k, long x) { // Voraz. Vamos lo más rápido que podamos pero asegurándonos de poder parar a tiempo. long speed = 0; long left = k; long t = 0; while (left > 0) { if (ok(speed + 1, x, left - (speed + 1))) { // aceleramos si podemos speed += 1; } else if (ok(speed, x, left - speed)) { // si no, mantenemos la misma velocidad si podemos } else { // frenamos si tenemos que // comprobamos que nos queda espacio suficiente para parar a tiempo. assert (ok(speed - 1, x, left - (speed - 1))); speed -= 1; } assert (speed >= 1); left -= speed; t++; } return t; } public static void main(String[] args) throws Exception { BufferedReader in = new BufferedReader(new FileReader("race.in")); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("race.out"))); String[] nk = in.readLine().split(" "); long k = Long.parseLong(nk[0]); int n = Integer.parseInt(nk[1]); for (int i = 0; i < n; i++) { long x = Long.parseLong(in.readLine()); out.println(solve(k, x)); out.flush(); } } }
# S(n) = 1+2+...+n = n*(n+1)/2 def S(n: int) -> int: return n * (n + 1) // 2 # A+(A+1)+(A+2)+...+B = \sum_{i=A}^B i def sum_(A: int, B: int) -> int: return S(B) - S(A - 1) if A <= B else 0 # Dado que empezamos a velocidad S, ¿podemos bajar a velocidad X en a lo sumo L metros? # ¿Cuántos metros toma parar si la velocidad actual es S y el objetivo es X? # (S-1) + (S-2) + ... + X metros # Se nos permite pasarnos, así que mientras tengamos (S-1)+(S-2)+...+(X+1)+1 m estamos bien. def ok(S: int, X: int, L: int) -> bool: if S <= X: return True need = sum_(X + 1, S - 1) + 1 return need <= L line1, *rest = open("race.in").readlines() k, n = [int(v) for v in line1.split()] X = [] for line in rest: X.append(int(line)) def solve(k, x): # Voraz. Vamos lo más rápido que podamos pero asegurándonos de poder parar a tiempo. speed = 0 left = k t = 0 while left > 0: if ok(speed + 1, x, left - (speed + 1)): # aceleramos si podemos speed += 1 elif ok(speed, x, left - speed): # si no, mantenemos la misma velocidad si podemos pass else: # frenamos si tenemos que # comprobamos que nos queda espacio suficiente para parar a tiempo. assert ok(speed - 1, x, left - (speed - 1)) speed -= 1 assert speed >= 1 left -= speed t += 1 return t with open("race.out", "w") as fout: for x in X: print(solve(k, x), file=fout)
Pista 1

¿Cuál es la mejor estrategia que Bessie podría usar si, hipotéticamente, no hubiera un límite de velocidad en la línea de llegada?

Respuesta a la Pista 1

¡Querríamos aumentar la velocidad tanto como podamos!

Pista 2

¿Cómo podemos usar esta estrategia cuando hay un límite de velocidad?

Respuesta a la Pista 2

Haremos que Bessie acelere de forma continua hasta que se vea forzada a frenar por el límite de velocidad. Esta estrategia funciona porque observamos que, cuando es posible, Bessie siempre debería acelerar.

Esto es un ejemplo de un algoritmo voraz (greedy).

Solución

Análisis oficial (C++) 

Explicación

De las pistas se ve claro que siempre queremos acelerar si es posible. ¿Pero cuándo deberíamos frenar? Para determinarlo, podemos iterar sobre nuestra velocidad pico y mantener la distancia actual recorrida hasta ahora, junto con la distancia necesaria para bajar a la velocidad máxima permitida en la línea de llegada. Si la distancia total cubierta al acelerar y desacelerar es mayor o igual que la longitud de la pista, entonces sabemos que alcanzamos nuestra velocidad óptima, y podemos devolver el tiempo correspondiente.

¿Por qué corre dentro del límite de tiempo?

Consideremos los siguientes hechos:

  • La cantidad de iteraciones que hacemos es directamente proporcional a la velocidad máxima
  • La velocidad máxima de Bessie es O(K)\mathcal{O}(\sqrt{K})

El primero es bastante directo, ya que iteramos directamente sobre nuestra velocidad máxima. Para entender el segundo hecho, consideremos que si Bessie no tuviera un límite de velocidad, su respuesta sería el primer valor de tt que satisface la siguiente desigualdad.

t(t+1)2K \frac{t(t+1)}{2} \geq K

tt es proporcional a K\sqrt{K}, así que nuestro código corre a tiempo.

Implementación

Complejidad temporal: O(QK)\mathcal{O}(Q \sqrt{K})

#include <fstream> #include <iostream> using std::cout; using std::endl; int fastest_time(int dist, int max_speed) { int speed_up_dist = 0; // Distancia en la que Bessie está acelerando int slow_down_dist = 0; // y frenando, respectivamente int time = 0; // Aceleramos de a poco hasta alcanzar nuestra distancia for (int curr_speed = 1;; curr_speed++) { speed_up_dist += curr_speed; time++; if (speed_up_dist + slow_down_dist >= dist) { return time; } /* * Si estamos por encima del límite de velocidad, sumamos la velocidad actual * también a la parte en la que frenamos. */ if (curr_speed >= max_speed) { slow_down_dist += curr_speed; time++; // Comprobamos de nuevo si llegamos o pasamos la línea de llegada if (speed_up_dist + slow_down_dist >= dist) { return time; } } } } int main() { std::ifstream read("race.in"); int dist; int query_num; read >> dist >> query_num; std::ofstream written("race.out"); for (int q = 0; q < query_num; q++) { int max_speed; read >> max_speed; written << fastest_time(dist, max_speed) << '\n'; } }
import java.io.*; import java.util.*; class Race { public static void main(String[] args) throws IOException { Scanner sc = new Scanner(new File("race.in")); int dist = sc.nextInt(); int queryNum = sc.nextInt(); PrintWriter pw = new PrintWriter(new File("race.out")); for (int q = 0; q < queryNum; q++) { int maxSpeed = sc.nextInt(); pw.println(fastestTime(dist, maxSpeed)); } pw.close(); } private static int fastestTime(int dist, int maxSpeed) { int speedUpDist = 0; // Distancia en la que Bessie está acelerando int slowDownDist = 0; // y frenando, respectivamente int time = 0; // Aceleramos de a poco hasta alcanzar nuestra distancia for (int currSpeed = 1;; currSpeed++) { speedUpDist += currSpeed; time++; if (speedUpDist + slowDownDist >= dist) { return time; } /* * Si estamos por encima del límite de velocidad, sumamos la velocidad actual * también a la parte en la que frenamos. */ if (currSpeed >= maxSpeed) { slowDownDist += currSpeed; time++; // Comprobamos de nuevo si alcanzamos nuestra distancia if (speedUpDist + slowDownDist >= dist) { return time; } } } } }
import math def fastest_time(dist: int, max_speed: int) -> int: # Calculamos el tiempo que toma si solo aceleramos no_slow_time = int(math.ceil((math.sqrt(8 * dist + 1) - 1) / 2)) if no_slow_time <= max_speed: return no_slow_time # Hay que acelerar más allá del límite, así que por ahora empezamos en la velocidad máxima speed_up_dist = max_speed * (max_speed - 1) // 2 slow_down_dist = 0 time = max_speed - 1 curr_speed = max_speed while True: speed_up_dist += curr_speed time += 1 if speed_up_dist + slow_down_dist >= dist: return time slow_down_dist += curr_speed time += 1 if speed_up_dist + slow_down_dist >= dist: return time curr_speed += 1 with open("race.in") as read: dist, query_num = map(int, read.readline().split()) with open("race.out", "w") as write: for _ in range(query_num): max_speed = int(read.readline()) write.write(str(fastest_time(dist, max_speed)) + "\n")