Ir al contenido (saltar navegación)

Plantillas de código para las soluciones

Todos los problemas propuestos en ¡Acepta el reto! se resuelven con aplicaciones de consola ("línea de órdenes"), que reciben datos por teclado (también conocido como entrada estándar) y escriben la respuesta como texto en pantalla (también conocida como salida estándar). Por tanto, resolver un problema supone entender el enunciado y desarrollar un algoritmo que resuelva lo que se está pidiendo leyendo por teclado y escribiendo en la pantalla.

Para que el juez pueda probar de forma exhaustiva que una solución enviada es correcta, ésta debe ser ejecutada múltiples veces de manera que el algoritmo se pruebe con muchos casos de prueba. Se entiende por caso de prueba los datos de entrada que el programa lee para ejecutar una vez el algoritmo que resuelve el problema. Por ejemplo, si el problema es, quitando la ambientación, decir si un número es par o impar, un caso de prueba sería un número. Si el problema pide decir si una lista está ordenada, un caso de prueba será, por ejemplo, la longitud de la lista y todos sus elementos.

Como se ha dicho, el juez automático querrá probar la solución con muchos casos de prueba, no solo con unos pocos. Para hacerlo sin tener que ejecutar la solución desde cero cada vez hay tres alternativas o esquemas de la entrada:

  • Al principio de la ejecución el programa recibe el número de casos de prueba que se deben procesar, seguido de los casos propiamente dichos.
  • El programa va leyendo casos de prueba hasta que encuentra uno especial que marca el final.
  • El programa va leyendo casos de prueba hasta que se alcanza el final de la entrada (no quedan más datos).

Prácticamente todos los problemas de ¡Acepta el reto! siguen uno de estos tres esquemas de la entrada (en realidad, todos menos uno). Esto fuerza a que las soluciones tengan un bucle externo para repetir la ejecución del algoritmo que resuelve el problema. Ese bucle será distinto dependiendo de cuál de los tres esquemas se utilice. Aunque puedes programar el bucle y el algoritmo todo junto, como consejo recomendamos que se utilice una función/método que se encargue de resolver un caso de prueba y que sea llamado desde el programa principal tantas veces como sea necesario. Eso te permite tener plantillas con el esqueleto de la solución y un "hueco" donde va el código principal del programa, que puede olvidarse de la repetición.

En esta página te proponemos plantillas para cada uno de los tres esquemas de la entrada. Ten en cuenta que el juez es estricto con el formato de la entrada y salida. Por ejemplo, si el enunciado dice que la entrada siempre será un número positivo, no debes preocuparte por reaccionar dignamente si no lo es, porque ese código nunca se ejecutará. De la misma forma, debes ser estricto con la salida. No escribas nada no pedido. En particular, en estos problemas no seas amigable con el usuario (¡el usuario es un ordenador!). Si escribes cosas como "Dame el siguiente número", confundirás al juez automático y te dirá que tu solución es incorrecta. Tienes más consejos básicos de este tipo en Resolviendo el primer problema.

Esquema 1: número de casos al principio

En este esquema de la entrada, el programa recibe un número inicial que indica cuantos casos de prueba deben procesarse. Es el esquema más sencillo. En el programa principal se lee ese número, y se realiza un bucle que repite el proceso tantas veces como se haya solicitado. Se usa, por ejemplo, en el problema 117 La fiesta aburrida o 216 Goteras.

En la plantilla que te proponemos, marcamos con "// TU CÓDIGO AQUÍ" el lugar donde debe ir la solución del problema ignorando la repetición. Por ejemplo, ahí iría la lectura del número y su análisis para decir si es par o impar.

12345678910111213141516171819
#include <stdio.h>

void casoDePrueba() {

    // TU CÓDIGO AQUÍ

} // casoDePrueba

int main() {

    unsigned int numCasos, i;

    scanf("%u\n", &numCasos);
    for (i = 0; i < numCasos; ++i)
        casoDePrueba();

    return 0;

}
12345678910111213141516171819
#include <iostream>
using namespace std;

void casoDePrueba() {

    // TU CÓDIGO AQUÍ

} // casoDePrueba

int main() {
    unsigned int numCasos;

    cin >> numCasos;
    for (unsigned int i = 0; i < numCasos; ++i) {
        casoDePrueba();
    }
  
    return 0;
}
12345678910111213141516171819
public class solution { // Asume fichero llamado solution.java

    static java.util.Scanner in;

    public static void casoDePrueba() {
      
        // TU CÓDIGO AQUÍ

    } // casoDePrueba

    public static void main(String[] args) {

        in = new java.util.Scanner(System.in);

        int numCasos = in.nextInt();
        for (int i = 0; i < numCasos; i++)
            casoDePrueba();
    } // main
} // class solution

Esquema 2: caso de prueba que marca el final

En este esquema de la entrada el programa debe ir procesando casos de prueba hasta que se encuentre con uno especial que marca el final, y que debe ignorarse. El enunciado indica las características de ese caso especial, que dependerá de cada problema. Es importante detectarlo bien, porque si la solución no se da cuenta de que ha leído el caso especial, seguirá "leyendo casos", lo que puede hacer que el programa termine con un TLE o con un RTE dependiendo de cómo reaccione la librería del lenguaje a la lectura por teclado de datos inexistentes.

En el esqueleto de solución que te proponemos, se implementa una función/método que procesa cada caso de prueba y que, además, devuelve si lo hizo o se llegó al final. La función lee el caso de prueba y si es el especial que marca el fin, termina devolviendo falso. En otro caso, procesa el caso de prueba y devuelve cierto.

Este esquema de la entrada lo utilizan, por ejemplo, los problemas 155 Perímetro de un rectángulo o 239 Pi. Pi. Pi. Pi. Pi. Piiiii.

12345678910111213141516171819202122
#include <stdio.h>

int casoDePrueba() {

    leer caso de prueba
    if (es el caso que marca el final)
        return 0; // false
    else {
        // CÓDIGO PRINCIPAL AQUÍ
        return 1; // true
     }

} // casoDePrueba

int main() {

    while(casoDePrueba()) {
    }

    return 0;

}
12345678910111213141516171819202122
#include <iostream>
using namespace std;

bool casoDePrueba() {

    leer caso de prueba
    if (es el caso que marca el final)
        return false;
    else {
        // CÓDIGO PRINCIPAL AQUÍ
        return true;
     }

} // casoDePrueba

int main() {

    while(casoDePrueba()) {
    }
  
    return 0;
}
12345678910111213141516171819202122
// Asume fichero llamado solution.java
public class solution {

    static java.util.Scanner in;

    public static boolean casoDePrueba() {
        leer caso de prueba
        if (es el caso que marca el final)
            return false;
        else {
            // CÓDIGO PRINCIPAL AQUÍ
            return true;
         }
    } // casoDePrueba

    public static void main(String[] args) {
        in = new java.util.Scanner(System.in);
        while (casoDePrueba()) {
        }
    } // main

} // class solution

Ten en cuenta que en la zona marcada con el "leer caso de prueba" es posible que no tengas que leer realmente todo el caso de prueba, sino únicamente la parte inicial suficiente como para saber que es el caso de prueba especial que marca el fin. En ese caso, en la sección marcada como "// CÓDIGO PRINCIPAL AQUÍ" tendrás que incluir la parte de la lectura del resto de la entrada.

Esquema 3: fin de la entrada

Este es el esquema menos habitual. En él, simplemente, deja de haber datos en la entrada, algo así como si el teclado se desconectara. Esto no es demasiado natural cuando la entrada es el teclado, pero sí lo es cuando es un fichero.

Para probar los problemas que siguen este esquema, cuando decidas que no quieres introducir más datos por teclado deberás pulsar Ctrl+Z e "Intro" si usas Windows, y Ctrl+D si usas GNU/Linux o Mac.

El esqueleto que te proponemos para las soluciones a los problemas que usan este esquema es similar al anterior. Implementaremos una función/método que procesa cada caso de prueba y que, además, indica si pudo hacerlo o no (se llegó al final).

Lo primero que hará será mirar si hay o no más datos de entrada. En algunos lenguajes (como C o C++), para hacer eso hay primero que intentar leer algo y luego mirar si funcionó. En otros (como en Java) se puede preguntar si queda algo en la entrada antes de leer. Una vez que se confirma que hay un caso de prueba siguiente, se continua normalmente.

Este esquema lo usan por ejemplo los problemas 147 Las 15 cerillas o 149 San Fermines.

12345678910111213141516171819202122
#include <stdio.h>


int casoDePrueba() {

    if (scanf(....) == EOF) // Leer el inicio del caso de prueba
        return 0; // false
    else {
        // CÓDIGO PRINCIPAL AQUÍ (incluyendo el resto de la lectura)
        return 1; // true
     }

} // casoDePrueba

int main() {

    while(casoDePrueba()) {
    }

    return 0;

}
12345678910111213141516171819202122
#include <iostream>
using namespace std;

bool casoDePrueba() {

    leer el inicio del caso de prueba (cin)
    if (!cin)
        return false;
    else {
        // CÓDIGO PRINCIPAL AQUÍ (incluyendo el resto de la lectura)
        return true;
    }

} // casoDePrueba

int main() {

    while(casoDePrueba()) {
    }
  
    return 0;
}
12345678910111213141516171819202122
// Asume fichero llamado solution.java
public class solution {

    static java.util.Scanner in;

    public static boolean casoDePrueba() {
        if (!in.hasNext())
            return false;
        else {
            // CÓDIGO PRINCIPAL AQUÍ
            // (incluyendo la lectura del caso de prueba)
            return true;
         }
    } // casoDePrueba

    public static void main(String[] args) {
        in = new java.util.Scanner(System.in);
        while (casoDePrueba()) {
        }
    } // main

} // class solution