Skip to Content

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:

  1. 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.
  2. 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: 11
  • BFB: 00, 22
  • BFFB: 11, 33
  • BFFFB: 00, 22, 44
  • BFFFFB: 11, 33, 55

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 nn, y nn es impar, nuestras soluciones son 0,2,4,n10, 2, 4, \ldots n-1. Si nn es par, nuestras soluciones son 1,3,5,n11, 3, 5, \ldots n-1.

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:

  1. 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”.
  2. 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 11: 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 n1n-1: deberíamos llenar todo con B’s, y nuestra respuesta es n1n-1.

Ahora volvamos al problema original (cuáles son todas las respuestas posibles). Si quisiera construir una respuesta de 22, ¿es posible? Intentemos con BFFB: ¡parece que no! Para pasar de una respuesta de 11 (BEBEBB) a una respuesta de 22, 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 33), no uno. Así, empezando con BEBEBB (la cadena que usamos para obtener una respuesta de 11), podemos cambiar repetidamente una E por una B para sumar dos a nuestra respuesta. Por lo tanto, podemos tener una respuesta de 1,3,5,n11, 3, 5, \ldots n-1.

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 nn y nn es impar, nuestro nivel mínimo de emoción es 11 y el máximo es n2n-2. Si nn es par, nuestro mínimo es 00 y el máximo es n2n-2. Sea xx nuestro mínimo e yy nuestro máximo. Podemos alcanzar cualquier nivel de emoción x,x+2,x+4,y2,yx, x+2, x+4, \ldots y-2, y.
  • EF...FE: La misma solución que BF...FB
  • BF...FBEF...FE: Parece que podemos partir la cadena en dos: BF...FB y EF...FE y 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 11 a cada una de las respuestas porque las dos BB del medio también aportan 11 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 nn, podemos obtener cualquier valor 0,1,2,3,n10, 1, 2, 3, \ldots n-1.
  • BEFBEFBEF: Si BEF se repite nn veces, nuestra respuesta es n1,nn-1, n.
  • BBB...: Si la longitud es nn, nuestra respuesta es n1n-1.
  • 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 nn, la respuesta es 0,1,n10, 1, \ldots n-1.

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:

  1. Agregar restricciones (simplificar el problema tanto como sea posible) hasta llegar a un problema que podamos resolver / demostrar.
  2. 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 nn, entonces hay 2n2^n cadenas posibles de este tipo (en el peor caso, cuando toda la cadena es F). Cada cadena tarda O(n)\mathcal{O}(n) en calcular una respuesta, así que en total esta solución tarda O(2nn)\mathcal{O}(2^n \cdot n).

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:

  • BFFFFFE
  • EB
  • BFE

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 nn la longitud de la sección.

Caso 1: El inicial / final es el mismo

Si nn es par, el mínimo es 11 y el máximo es n1n-1. En caso contrario, el mínimo es 00 y el máximo es n1n-1.

Caso 2: El inicial / final es distinto

Si nn es par, el mínimo es 00 y el máximo es n2n-2. En caso contrario, el mínimo es 11 y el máximo es n2n-2.


Sea xx el mínimo e yy el máximo. Podemos alcanzar cualquier valor x,x+2,x+4,y2,yx, x+2, x+4, \ldots y-2, y.

Empezamos con la cadena que produce xx; 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 11.

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 xx es el mínimo de toda la cadena e yy es el máximo de toda la cadena, nuestras respuestas posibles son x,x+2,x+4,,y2,yx, x+2, x+4, \ldots, y-2, y.

Manejar las F’s en el primer / último carácter

Sea xx la cantidad de F’s al inicio de la cadena e yy la cantidad de F’s al final de la cadena. Por inspección, estas F’s aportan un mínimo de 00 a nuestro nivel de emoción y un máximo de x+yx + y 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 00 y x+yx+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 xx o yy no sea 00, 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 xx como yy son 00, entonces seguimos pudiendo obtener solo niveles de emoción en incrementos de 22.

También hay que tener cuidado de manejar correctamente el caso en el que toda la cadena es F’s.

Implementación

Complejidad temporal: O(N)\mathcal{O}(N)

#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)