Está en la página 1de 7

Búsqueda Binaria

Miguel Miní

Outline

• ¿Qué es búsqueda binaria?


– ¿Cómo descartamos la mitad de los casos?
– Código solución Guest the Number
– Complejidad
– Dos problemas ligeramente distintos
– ¿Qué debe cumplir la condición P?
– Implementaciones ligeramente distintas
• Problemas sencillos
– Codeforces / Interesting Drink
– Hackerrank / Find the Median
– Uva / Exact Sum
– Codeforces / Pipeline
• [CONTEST LINK]

¿Qué es búsqueda binaria?

Supongamos que en el típico juego de adivinanzas: Dado n, el retador elije un número del 1 al n, y nosotros,
el desdichado que ha sido retado, debe adivinar qué número es n, solo podemos elegir un número a la vez, y
nuestro retador nos dirá si hemos adivinado, o si el número es mayor o menor.
Cómo nuestro amigo el retador es muy listo, nos dá un número k limitado de intentos. ¿Cómo podemos hacer
nuestro mejor intento? ¿Se puede hallar un k mìnimo, después de obtener el n, tal que siempre tendremos
la certeza de poder adivinar?

Figure 1: binary-search

Se puede encontrar una versión similar del problema en el siguiente link: Guess the Number
Para afrontar nuestras interrogantes, nosotros debemos tratar de descartar la mayor cantidad de candidatos.
Claramente no hay mucho que discutir en este aspecto, y obviamente debo descartar al menos la mitad de

1
los elementos. En nuestro problema esto se puede lograr preguntando por el elemento del medio sobre todos
los candidatos [l, r], al recibir un “>=” nosotros podemos estar seguro que el elemento esta en el rango [mid,
r] y en caso contrario, en el rango [l, mid − 1]. nosotros podemos estar tentados a tomar en vez del medio,
tal vez el que esta a dos tercios del menor valor (l), pero esto debemos dejarlo de lado, por que en el peor
de los casos nos darán un “>=”, y estamos interesados en la mejor opcion para cualquier escenario.

¿Cómo descartamos la mitad de los casos?

Casi siempre nos veremos con problemas en el cual tenemos un rango continuo, y tenemos una forma de
decidir con los número mayor o igual (mayor) o con los numeros menor (menor o igual), nos basta con elegir
la mitad en estos casos, pero ¿en el caso de dos elementos? ¿qué elemento está en el medio?

Figure 2: reduciendo_intervalo

Código solución Guest the Number

//codeforces/gym/101021 Guest the Number


#include <bits/stdc++.h>
using namespace std;

bool query(int x) {
cout << x << endl;
string res;
cin >> res;
return res == ">=";
}

int main() {
int lo = 1, hi = 1e6;
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2;
if (query(mid)) lo = mid;
else hi = mid - 1;
}
cout << "! " << lo << endl;
return 0;
}

2
Complejidad:

La complejidad de la solución anterior, es O(log n). Esto es facilmente comprobable del hecho que siempre
reduzco a la mitad mi intervalo de busqueda, así en terminos sencillos la cantidad de veces que particiono el
problema es:

T (n) = T (n/2) + 1
que tiene el siguiente desarrollo:

k log(n)
n X X
T (n) = T ( k ) + 1 = T (1) + 1 = O(log(n))
2 i=0 i=0

Dos problemas ligeramente distintos

Los problemas de busqueda binaria se nos presentarán de las siguientes formas, cualquier otra forma pueder
ser transformada fácilmente en alguna de estas.

• forma 1: Hallar el último elemento que cumpla una condición.

• forma 2: Hallar el primer elemento que no cumpla una condición.

Figure 3: forma 1 y forma 2

nota: tenga en cuenta que al decir el ùltimo que cumpla, nos da una sensación que se cumple hasta un cierto
valor.

¿Qué debe cumplir la condición P?

No toda condición tiene la forma que quisieramos, para nuestros fines debe tener la forma: Si P(x), entonces
P(x-1), o lo que es similar si no P(x-1) entonces no P(x). Esto quiere decir que la condición cumplen hasta
un cierto valor y en adelante nunca más cumple (o nunca cumple o siempre cumple).

3
Implementaciones ligeramente distintas

Para la forma 1, utilizaremos un código que respete la siguiente invariante: “Siempre mi rango comenzará
con un elemento que cumple P”.

int lo = min_answer, hi = max_answer;


while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2;
if (P(mid)) lo = mid;
else hi = mid-1;
}
cout << lo << endl;

Para la implementación yo debo hacer la siguiente decisión: ¿Debo dividir el rango por la mitad inferior o
la mitad superior? ¿Importa?
Como nuestro algoritmo debe ser consistente para todos los escenarios, primero descartemos cuando el rango
es impar, ahí solo hay un medio que elegir, asì que enfoquemonos en el caso cuando tengo una cantidad
de elementos par, si nosotros queremos encontrar el último elemento que cumple, claramente al caer en un
elemento y que este no cumple, nuestro nuevo rando es [l, mid − 1] y en caso contrario, nuestro rango de
busqueda es [mid, r], para que los dos sean lo más balanceados posible, deben ser del mismo tamaño así:

r − mid + 1 = mid − l

por tanto:
mid = l + (r − l + 1)/2
nota: he separado el l, porque las divisiones de negativos, no son tan sencillas de predecir.
Teniendo el mismo análisis, para el caso en que quiero hallar el primer elemento que falla bastaría con:

mid − l + 1 = r − mid
mid = l + (r − l − 1)/2 = l + (r − l)/2
nota: le sumo 1 para manejar el caso de tamaño par también.

int lo = min_answer, hi = max_answer;


while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (P(mid)) lo = mid+1;
else hi = mid;
}
cout << lo << endl;

Problemas Sencillos:

1. Interesting Drink

problema: Dado n números xi , y q queries mi hallar cuantos números xi son menor o igual a mi .
solución: La condición P es obvia: “los k menores elementos son menor o igual a mi ”. El problema
solamente es saber cuales son los primeros k, pero esto es fácilmente solucinable ordenando los xi . Es claro
que necesito el último k que cumpla.

4
#include <bits/stdc++.h>
using namespace std;

int main() {
int n, q;
cin >> n;
vector<int> x(n);
for (int i = 0; i < n; ++i) {
cin >> x[i];
}
sort(x.begin(), x.end());
cin >> q;
while (q--) {
int m;
cin >> m;
int lo = 1, hi = n;
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2;
if (x[mid - 1] <= m) lo = mid;
else hi = mid-1;
}
if (x[lo - 1] > m) cout << 0 << endl;
else cout << lo << endl;
}
return 0;
}

2. Find the Median

problema: dado un array, hallar la mediana.


solución: La condición es: “Hay menor que (n+1)/2 elementos menores o iguales que x”. En este caso
queremos encontrar el primero que falla.

#include <bits/stdc++.h>
using namespace std;

int n;
bool P(vector<int>& arr, int x) {
int ans = 0;
for (int v : arr) {
if (v <= x) {
ans += 1;
}
}
return ans < (n+1)/2;
}

int main() {
cin >> n;
vector<int> arr(n);
for (int& x : arr) cin >> x;
int lo = -10000, hi = 10000; //note que el rango puede tomar valores negativos.

5
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (P(arr, mid)) lo = mid+1;
else hi = mid;
}
cout << lo << endl;
return 0;
}

3. Exact Sum

problema: Dado n números y un número m, encontrar dos de estos números que sumen exactamente m, y
que su diferencia sea mínima.
solución: Supongamos que los elementos están ordenados, entonces, basta con seleccionar uno de ellos y
considerar como rango a buscar todos los elementos adelante de el (y usamos busqueda binaria).

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
int a[maxn];
int n, m;

int main() {
while (cin >> n) {
for (int i = 0; i < n; ++i) {
scanf("%d", a+i);
}
sort(a, a+n);
scanf("%d", &m);
int x, y;
for (int i = 0; i + 1 < n; ++i) {
int lo = i+1, hi = n-1;
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2;
if (a[i] + a[mid] <= m) lo = mid;
else hi = mid-1;
}
if (a[i] + a[lo] == m) {
x = a[i];
y = a[lo];
}
}
cout << "Peter should buy books whose prices are "<<x<<" and "<<y<<".\n" << endl;
}
return 0;
}

4. Pipeline

problema: Dado los números del 1 al k, escoger x nùmeros de ellos tal que su suma menos la cantidad de
elementos de exactamente n, ademàs, siempre se debe usar el 1.

6
solucion: Es fácil de probar que todo nùmero puede ser generado si su valor es menor o igual a k(k−1) 2 ,
luego la función es creciente en el eje positivo, por tanto, podemos hacer una busqueda si se puede formar o
no, como punto clave, si quiero saber si se puede formar un determinado nùmero basta con que los x últimos
menos x sumen al menos n-1.

#include <bits/stdc++.h>
using namespace std;
long long n, k;

long long p(long long x) {


return k * (k + 1) / 2 - (k-x) * (k-x+1) / 2 - x + 1;
}

int main() {
cin >> n >> k;
long long lo = 0, hi = k-1;
while (lo < hi) {
long long mid = lo + (hi - lo) / 2;
if (p(mid) < n) lo = mid + 1;
else hi = mid;
}
if (p(lo) >= n) cout << lo << endl;
else cout << -1 << endl;
return 0;
}

CONTEST LINK

También podría gustarte