FEB
Nota: Esta sección pretende guiar a través del proceso de resolución de problemas para llegar a la solución, en lugar de solo enunciar la solución de inmediato; idealmente, este proceso de resolución se puede aplicar también a otros problemas.
Si hay comentarios sobre qué tan bien funciona este formato / si esta sección resultó útil, y la fecha actual todavía no es abril de 2024, se puede escribir a nathan.r.wang@gmail.com.
Proceso
Si solo se quiere la solución completa, hay que bajar a la sección “Explicación” más abajo.
Paso 1: Resolver las entradas de ejemplo
El primer paso es resolver las entradas de ejemplo a mano. El objetivo es asegurarse de entender por completo lo que el problema pide hacer.
Paso 2: Inventar más casos de prueba
A continuación, intentemos inventar más casos de prueba que podamos resolver a mano. Concentrémonos en dos tipos de casos:
- Muchos casos de prueba simples que tengan algún patrón. Queremos usarlos para descubrir patrones que tal vez podamos usar para resolver el problema. Ejemplos:
BFB,BFFB,BFFBFFB, etc. - Casos borde — tantos como se nos ocurran.
A medida que inventamos estos casos de prueba, hay que intentar agruparlos en patrones (sobre todo los casos simples). Por ejemplo, BFB, BFFB, BFFFB se pueden agrupar en un patrón como BF...FB. El objetivo es llegar a soluciones para cada uno de estos patrones, y luego intentar llegar a una solución del problema general.
Inventé unos 30 casos de prueba, pero el número exacto no importa mucho. No hay que gastar demasiado tiempo en esto (¿yo tardé unos 4 minutos?); siempre se pueden inventar más después.
Solución
Está totalmente bien si los casos de prueba / patrones que se te ocurrieron son distintos.
Casos de prueba simples
- Patrón: BF…FB. Casos:
BB,BFB,BFFB,BFFFB,BFFFFB - Patrón: BF…FE. Casos:
BE,BFE,BFFE,BFFFE,BFFFFE - Patrón: EF…FE. Casos:
EE,EFE,EFFE,EFFFE,EFFFFE - Patrón: BF…FBEF…FE. Casos:
BFBEFE,BFFBEFFE,BFFFBEFE - Patrón: BF…FBBF…FB. Casos:
BFBBFB,BFFBBFFB - Patrón: BF…FBF…FB. Casos:
BFBFB,BFFBFB - Patrón: F…FB. Casos:
B,FB,FFB,FFFB - Patrón: BEF…. Casos:
BEF,BEFBEF,BEFBEFBEF - Patrón: BBB…. Casos:
B,BB,BBB,BBBB
Casos borde
- Solo B’s:
BBBB - Solo E’s:
EEEE - Sin F’s:
BEBEBEBBBEEBEB - Solo F’s:
FFFFF
Paso 3: Resolver los casos de prueba a mano
Ahora resolveremos a mano los casos de prueba que inventamos antes, intentando descubrir patrones en cómo los resolvemos. No todas las categorías de casos tendrán soluciones generalizables / bonitas, y está bien.
Como ejemplo, esto es lo que escribí para uno de los patrones que se me ocurrieron.
Patrón 1: BF…FB
BB:BFB: ,BFFB: ,BFFFB: , ,BFFFFB: , ,
Posible solución
¿Se ve algún patrón? (Quizá haga falta escribir más casos.)
Solución
Si la longitud de la cadena es , y es impar, nuestras soluciones son . Si es par, nuestras soluciones son .
Llegué a la solución anterior solo por reconocimiento de patrones; no tengo idea de si es correcta. Intentemos convencernos de por qué es correcta (o de si en realidad está mal).
¿Es posible convencerse de que esta solución es correcta?
Si uno se traba, se puede usar esta estrategia:
- Agregar restricciones (simplificar el problema tanto como sea posible) hasta llegar a un problema que podamos resolver / demostrar. Por ejemplo, una restricción que podríamos agregar es “suponer que la longitud de la cadena es par”.
- Reincorporar las restricciones de a una y actualizar la solución / demostración hasta volver al problema original.
Solución
Resolver los casos par / impar por separado. Para el caso par, primero intentemos responder la pregunta más fácil: “¿cuál es nuestra respuesta mínima / máxima”?
Nuestro mínimo es : deberíamos alternar BEBEBEB tanto como sea posible. Lamentablemente, como la longitud es par, al final nos vemos forzados a tener dos B’s seguidas, así que nuestra respuesta mínima es 1.
Nuestro máximo es : deberíamos llenar todo con B’s, y nuestra respuesta es .
Ahora volvamos al problema original (cuáles son todas las respuestas posibles). Si quisiera construir una respuesta de , ¿es posible? Intentemos con BFFB: ¡parece que no! Para pasar de una respuesta de (BEBEBB) a una respuesta de , hay que cambiar una E para que se vuelva B. Pero al hacerlo en realidad se suma dos a nuestra respuesta (así que podemos obtener ), no uno. Así, empezando con BEBEBB (la cadena que usamos para obtener una respuesta de ), podemos cambiar repetidamente una E por una B para sumar dos a nuestra respuesta. Por lo tanto, podemos tener una respuesta de .
Ahora que resolvimos el caso par, podemos aplicar una solución similar para resolver el caso impar.
Probarlo por cuenta propia
En este paso, se puede seguir resolviendo los casos de prueba que se me ocurrieron, o también usar los casos de prueba propios.
Para cada patrón, hay que intentar llegar a una posible solución, y también a una justificación de por qué esa solución es correcta. No todos los patrones tendrán soluciones bonitas: si no se ocurre nada, ¡simplemente hay que seguir adelante! (Tampoco hay que asustarse si la solución tiene un montón de casos distintos: las soluciones “bonitas” igual pueden tener casos distintos.)
También puede servir saltear un patrón complicado y volver a él más tarde, después de haber resuelto algunos patrones más simples. A veces la solución de un patrón complicado es solo una combinación de las soluciones de dos patrones más fáciles.
Solución
Disculpas por la brevedad de estas soluciones; escribirlas todas por completo tardaría demasiado…
BF...FE: Si la longitud es y es impar, nuestro nivel mínimo de emoción es y el máximo es . Si es par, nuestro mínimo es y el máximo es . Sea nuestro mínimo e nuestro máximo. Podemos alcanzar cualquier nivel de emoción .EF...FE: La misma solución queBF...FBBF...FBEF...FE: Parece que podemos partir la cadena en dos:BF...FByEF...FEy sumar sus respuestas (es decir, tomar una respuesta posible de la primera cadena y otra respuesta posible de la segunda y sumarlas para obtener una respuesta posible de las cadenas combinadas).BF...FBBF...FB: Podemos hacer lo mismo aquí, excepto que hay que sumar a cada una de las respuestas porque las dos del medio también aportan a la respuesta.BF...FBF...FB: La respuesta es la misma que la del patrón anterior menos uno.F...FB: Si la longitud de la cadena es , podemos obtener cualquier valor .BEFBEFBEF: Si BEF se repite veces, nuestra respuesta es .BBB...: Si la longitud es , nuestra respuesta es .- Solo B’s: Ya cubierto arriba
- Solo E’s: Igual que solo B’s. En general, no importa si se invierten las letras
- Sin F’s: Se puede resolver en tiempo lineal, no es muy interesante
- Solo F’s: Si la longitud es , la respuesta es .
Paso 4: Construir una solución general
Ahora, intentemos llegar a una solución del problema general usando algunas de las soluciones que obtuvimos antes.
Si uno se traba, la estrategia de arriba sigue aplicando:
- Agregar restricciones (simplificar el problema tanto como sea posible) hasta llegar a un problema que podamos resolver / demostrar.
- Reincorporar las restricciones de a una y actualizar la solución / demostración hasta volver al problema original.
También se puede volver al paso 2 (inventar casos de prueba chicos nuevos / patrones nuevos e intentar hallar soluciones para esos) si uno se traba.
Pista 1
Tener F’s al inicio / al final parece hacer el problema un poco más difícil. Limitémonos al caso en el que el carácter inicial / final no es F, y luego reincorporemos la restricción de las F más adelante.
Pista 2
La solución de BF...FBEF...FE era partir la subcadena en dos y “combinar” las soluciones. ¡Esto parece prometedor!
- ¿Podemos partir la cadena más de una vez?
- En el caso general, ¿cuándo deberíamos “partir” la subcadena?
- ¿Cómo “combinamos” las soluciones de forma eficiente?
Reflexiones finales
¡Espero que esta sección sobre el proceso de resolución que usé para resolver este problema haya sido útil! Este proceso no es específico de este problema, así que ojalá se pueda aplicar un proceso similar a otros problemas también.
Si hay comentarios sobre si esta sección resultó útil / qué se puede mejorar, y la fecha actual todavía no es abril de 2024, se puede escribir a nathan.r.wang@gmail.com.
Explicación
Subtarea 1
Para la subtarea 1, podemos generar por fuerza bruta cada cadena S posible. Si la cadena tiene longitud , entonces hay cadenas posibles de este tipo (en el peor caso, cuando toda la cadena es F). Cada cadena tarda en calcular una respuesta, así que en total esta solución tarda .
Problema completo
Primero, supongamos que el carácter inicial / final no es F.
Partimos la cadena en “secciones” tales que cada sección tiene un carácter inicial que no es F, un carácter final que no es F, y un montón de F’s en el medio. Los caracteres de los bordes de cada “sección” pueden solaparse. Para la entrada de ejemplo BFFFFFEBFE, nuestras secciones son:
BFFFFFEEBBFE
Obtendremos una respuesta para cada sección y luego combinaremos las respuestas para obtener la respuesta de toda la cadena.
Resolver cada sección
Empecemos hallando la emoción mínima / máxima posible de cada sección. Lo haremos con análisis por casos:
- ¿El carácter inicial / final es el mismo?
- ¿La longitud de la sección es par?
Sea la longitud de la sección.
Caso 1: El inicial / final es el mismo
Si es par, el mínimo es y el máximo es . En caso contrario, el mínimo es y el máximo es .
Caso 2: El inicial / final es distinto
Si es par, el mínimo es y el máximo es . En caso contrario, el mínimo es y el máximo es .
Sea el mínimo e el máximo. Podemos alcanzar cualquier valor .
Empezamos con la cadena que produce ; es algo como BEBEBEB. Cambiar una E para que se vuelva B aumenta nuestra respuesta en 2.
Si examinamos algo como BFFFB, observemos que es imposible alcanzar una respuesta de .
Combinar secciones
Podemos sumar los valores mínimo / máximo de cada sección para obtener los valores mínimo / máximo de toda la cadena. Si es el mínimo de toda la cadena e es el máximo de toda la cadena, nuestras respuestas posibles son .
Manejar las F’s en el primer / último carácter
Sea la cantidad de F’s al inicio de la cadena e la cantidad de F’s al final de la cadena. Por inspección, estas F’s aportan un mínimo de a nuestro nivel de emoción y un máximo de a nuestro nivel de emoción.
Observemos que en este caso especial en el que F está en el borde de la cadena, ¡en realidad podemos alcanzar cualquier valor entre y ! Por ejemplo, tomemos la cadena FFFB. Podemos obtener 0 haciendo EBEB, 1 haciendo BEBB, 2 haciendo EBBB, y 3 haciendo BBBB.
Como resultado, siempre que uno de o no sea , podemos alcanzar un nivel de emoción para nuestra cadena global de cada valor entre el mínimo y el máximo. Sin embargo, si tanto como son , entonces seguimos pudiendo obtener solo niveles de emoción en incrementos de .
También hay que tener cuidado de manejar correctamente el caso en el que toda la cadena es F’s.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
string s;
cin >> s;
int mn = 0, mx = 0;
int cur_idx = 0;
while (cur_idx < n) {
if (s[cur_idx] == 'F') {
cur_idx++;
continue;
}
int next_idx = cur_idx + 1;
while (next_idx < n && s[next_idx] == 'F') { next_idx++; }
if (next_idx == n) { break; }
int num_F = next_idx - cur_idx - 1;
if (s[next_idx] == s[cur_idx]) {
// Caso 1: Resolver algo como BFFFFFB
int length = num_F + 2;
// Primero, resolver mn
if (length % 2 == 0) {
// En el mejor caso podemos tener algo como BEBEBB
mn += 1;
} else {
// Podemos alternar BEBEB
mn += 0;
}
// Luego, resolver mx
mx += length - 1;
} else {
// Caso 2: Resolver algo como BFFFFFE
int length = num_F + 2;
// Primero, resolver mn
if (length % 2 == 0) {
// Podemos alternar BEBEBE
mn += 0;
} else {
// En el mejor caso tenemos BEBEE
mn += 1;
}
// Luego, resolver mx
mx += length - 2;
}
cur_idx = next_idx;
}
// Caso especial: F's al inicio / al final
int num_beginning_F = 0;
while (num_beginning_F < n && s[num_beginning_F] == 'F') num_beginning_F++;
int num_ending_F = 0;
while (num_ending_F < n && s[n - 1 - num_ending_F] == 'F') num_ending_F++;
if (num_beginning_F == n) {
// ¡todo es F! caso especial
mn = 0;
mx = n - 1;
} else {
// mn no cambia, ya que siempre podemos alternar el inicio /
// el final de forma correcta
mx += num_beginning_F;
mx += num_ending_F;
}
vector<int> possible_levels;
if (num_beginning_F == 0 && num_ending_F == 0) {
// Caso especial: solo podemos cambiar en incrementos de dos
assert((mx - mn) % 2 == 0);
for (int i = mn; i <= mx; i += 2) possible_levels.push_back(i);
} else {
for (int i = mn; i <= mx; i++) possible_levels.push_back(i);
}
cout << possible_levels.size() << endl;
for (int i : possible_levels) { cout << i << endl; }
}import java.io.*;
import java.util.*;
public class FEB {
public static void main(String[] args) {
Kattio io = new Kattio();
int n = io.nextInt();
String s = io.next();
int mn = 0, mx = 0;
int curIdx = 0;
while (curIdx < n) {
if (s.charAt(curIdx) == 'F') {
curIdx++;
continue;
}
int nextIdx = curIdx + 1;
while (nextIdx < n && s.charAt(nextIdx) == 'F') { nextIdx++; }
if (nextIdx == n) { break; }
int numF = nextIdx - curIdx - 1;
if (s.charAt(nextIdx) == s.charAt(curIdx)) {
// Caso 1: Resolver algo como BFFFFFB
int length = numF + 2;
// Primero, resolver mn
if (length % 2 == 0) {
// En el mejor caso podemos tener algo como BEBEBB
mn += 1;
} else {
// Podemos alternar BEBEB
mn += 0;
}
// Luego, resolver mx
mx += length - 1;
} else {
// Caso 2: Resolver algo como BFFFFFE
int length = numF + 2;
// Primero, resolver mn
if (length % 2 == 0) {
// Podemos alternar BEBEBE
mn += 0;
} else {
// En el mejor caso tenemos BEBEE
mn += 1;
}
// Luego, resolver mx
mx += length - 2;
}
curIdx = nextIdx;
}
// Caso especial: F's al inicio / al final
int numBeginningF = 0;
while (numBeginningF < n && s.charAt(numBeginningF) == 'F') { numBeginningF++; }
int numEndingF = 0;
while (numEndingF < n && s.charAt(n - 1 - numEndingF) == 'F') { numEndingF++; }
if (numBeginningF == n) {
// ¡todo es F! caso especial
mn = 0;
mx = n - 1;
} else {
// mn no cambia, ya que siempre podemos alternar el inicio /
// el final de forma correcta
mx += numBeginningF;
mx += numEndingF;
}
List<Integer> possibleLevels = new ArrayList<>();
if (numBeginningF == 0 && numEndingF == 0) {
// Caso especial: solo podemos cambiar en incrementos de dos
assert (mx - mn) % 2 == 0;
for (int i = mn; i <= mx; i += 2) { possibleLevels.add(i); }
} else {
for (int i = mn; i <= mx; i++) { possibleLevels.add(i); }
}
io.println(possibleLevels.size());
for (int i : possibleLevels) { io.println(i); }
io.close();
}
// CodeSnip{Kattio}
}n = int(input())
s = input()
mn = 0
mx = 0
cur_idx = 0
while cur_idx < n:
if s[cur_idx] == "F":
cur_idx += 1
continue
next_idx = cur_idx + 1
while next_idx < n and s[next_idx] == "F":
next_idx += 1
if next_idx == n:
break
num_F = next_idx - cur_idx - 1
if s[next_idx] == s[cur_idx]:
# Caso 1: Resolver algo como BFFFFFB
length = num_F + 2
# Primero, resolver mn
if length % 2 == 0:
# En el mejor caso podemos tener algo como BEBEBB
mn += 1
else:
# Podemos alternar BEBEB
mn += 0
# Luego, resolver mx
mx += length - 1
else:
# Caso 2: Resolver algo como BFFFFFE
length = num_F + 2
# Primero, resolver mn
if length % 2 == 0:
# Podemos alternar BEBEBE
mn += 0
else:
# En el mejor caso tenemos BEBEE
mn += 1
# Luego, resolver mx
mx += length - 2
cur_idx = next_idx
# Caso especial: F's al inicio / al final
num_beginning_F = 0
while num_beginning_F < n and s[num_beginning_F] == "F":
num_beginning_F += 1
num_ending_F = 0
while num_ending_F < n and s[n - 1 - num_ending_F] == "F":
num_ending_F += 1
if num_beginning_F == n:
# ¡todo es F! caso especial
mn = 0
mx = n - 1
else:
# mn no cambia, ya que siempre podemos alternar el inicio /
# el final de forma correcta
mx += num_beginning_F
mx += num_ending_F
possible_levels = []
if num_beginning_F == 0 and num_ending_F == 0:
# Caso especial: solo podemos cambiar en incrementos de dos
assert (mx - mn) % 2 == 0
for i in range(mn, mx + 1, 2):
possible_levels.append(i)
else:
for i in range(mn, mx + 1):
possible_levels.append(i)
print(len(possible_levels))
for i in possible_levels:
print(i)El código de Ben, que es bastante más conciso y aprovecha algunas similitudes agradables entre casos:
#include <algorithm>
#include <iostream>
#include <vector>
using std::endl;
using std::vector;
int main() {
int n;
std::string s;
std::cin >> n >> s;
if (std::count(s.begin(), s.end(), 'F') == n) { s[0] = 'E'; }
vector<int> positions;
for (int i = 0; i < n; i++) {
if (s[i] != 'F') { positions.push_back(i); }
}
int ones = positions[0] + n - 1 - positions.back();
int mn = 0;
int mx = 0;
for (int i = 0; i < positions.size() - 1; i++) {
int a = positions[i];
int b = positions[i + 1];
mn += ((b - a) & 1) ^ (s[a] != s[b]);
mx += b - a - (s[a] != s[b]);
}
vector<int> ans;
for (int i = mn; i <= ones + mx; i += (ones == 0 ? 2 : 1)) { ans.push_back(i); }
std::cout << ans.size() << endl;
for (int i : ans) { std::cout << i << endl; }
}import java.io.*;
import java.util.*;
public class FEB {
public static void main(String[] args) {
Kattio io = new Kattio();
int n = io.nextInt();
String s = io.next();
if (s.chars().filter(c -> c == 'F').count() == n) { s = "E" + s.substring(1); }
List<Integer> positions = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (s.charAt(i) != 'F') { positions.add(i); }
}
int ones = positions.get(0) + n - 1 - positions.get(positions.size() - 1);
int mn = 0;
int mx = 0;
for (int i = 0; i < positions.size() - 1; i++) {
int a = positions.get(i);
int b = positions.get(i + 1);
mn += ((b - a) & 1) ^ (s.charAt(a) != s.charAt(b) ? 1 : 0);
mx += b - a - (s.charAt(a) != s.charAt(b) ? 1 : 0);
}
List<Integer> ans = new ArrayList<>();
for (int i = mn; i <= ones + mx; i += (ones == 0 ? 2 : 1)) { ans.add(i); }
io.println(ans.size());
for (int i : ans) { io.println(i); }
io.close();
}
// CodeSnip{Kattio}
}N = int(input())
S = list(input())
if S.count("F") == N:
S[0] = "E"
positions = [i for i in range(N) if S[i] != "F"]
ones = positions[0] + N - 1 - positions[-1]
mn, mx = 0, 0
for a, b in zip(positions, positions[1:]):
mn += ((b - a) & 1) ^ (S[a] != S[b])
mx += b - a - (S[a] != S[b])
ans = range(mn, ones + mx + 1, 1 + (ones == 0))
print(len(ans))
for level in ans:
print(level)