Mostrando entradas con la etiqueta Arreglos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Arreglos. Mostrar todas las entradas

Chef and Digits (ADIGIT) - Editorial

Link al problemaADIGIT (Practice)
Link al editorial en CodeChefADIGIT - Editorial

Dificultad: Fácil

Problema:
Dados $N$ ($N ≤ 10^5$) dígitos y $M$ ($M ≤ 10^5$) consultas que consisten en un entero $x$ ($1 ≤ x ≤ N$), que llamaremos step (paso), imprimir el resultado de $B1 - B2$, donde:
  • $B1$ := Sumatoria de todos los $b = a_x - a_y$ tal que $x > y$ y $b > 0$
  • $B2$ := Sumatoria de todos los $b = a_x - a_y$ tal que $x > y$ y $b < 0$

Solución:
Veamos el siguiente caso, con entrada a = 0123152397

Calculando los valores para todos los pasos tenemos lo siguiente:

En la tabla anterior he marcado dos casillas, para observar a detalle lo que sucede:
  • Para el paso 4 tenemos: $B1 = (3 -0) + (3 - 1) + (3 - 2) = 6$ y $B2 = 0$ lo cual nos da el valor de $6$
  • Para el paso 8 tenemos: $B1 = (3 - 0) + (3 - 1)+  (3 - 2) + (3 - 1) + (3 - 2) = 9$ y $B2 = (3 - 5) = - 2$, por lo tanto tenemos $B1 - B2 = 11$
Observando las ecuaciones anteriores, podemos notar que el paso 11 se compone del paso 3 en las sumas $(3 - 0) + (3 - 1)+ (3 - 2)$ más otras 3 sumas, así que ¿qué pasaría si los almacenamos para no volver a calcularlo todo? Basándonos en eso, almacenamos los resultados en otro arrelgo.

Ahora, ¿cómo es que sabemos que tenemos un valor ya calculado?, esto es sencillo de encontrar y ocurre cuando los números $a_x$ y $a_y$ son iguales. Si los calculamos de manera secuencial, es decir de 1 hasta N, estaremos garantizando que para cada paso los pasos previos ya estarían precalculados, como solo hay 10 dígitos distintos, en el peor de los casos terminaremos sumando 10 veces. Al terminar el precálculo, para cada consulta que nos hagan la respuesta será obtenida en tiempo constante

Complejidad: $O(10 * N) + O(M)$

Código (C++):
Definiremos un arreglo A, donde A[i] es es valor de paso i-ésimo del problema y s, un arreglo de char, donde s[j] es la posición del dígito j-esimo.
void precalc(){
int t = 0;
for(int step = 0; step < n; step++){
int ans = 0;
for(int i = step-1; i >= 0; i--){
t = (s[step] - '0') - (s[i] - '0');
if(t == 0){
ans += A[i];
A[step] = ans;
break;
}
ans += abs(t);
A[step] = ans;
}
}
}
Haciendo que para responder a las consultas q, solo será necesario imprimir el valor almacenado en A[q-1]
int main(){
scanf("%d%d",&n,&m);
scanf("%s",s);
precalc();
while(m--){
scanf("%d",&q);
printf("%d\n",A[q-1]);
}
return 0;
}

¡Saludos!
@fferegrino :)

Programación dinámica y la sucesión de Fibonacci

(Wikipediazo)En informática, la programación dinámica es un método para reducir el tiempo de ejecución de un algoritmo mediante la utilización de subproblemas superpuestos y subestructuras óptimas.

Decir que un problema tiene subproblemas superpuestos es decir que se usa un mismo subproblema para resolver diferentes problemas mayores. Por ejemplo, en la sucesión de Fibonacci (F3 = F1 + F2 y F4 = F2 + F3) calcular cada término supone calcular F2. Como para calcular F5 hacen falta tanto F3 como F4, una mala implementación para calcular F5 acabará calculando F2 dos o más veces.

Esto se puede evitar guardando las soluciones que ya hemos calculado. Entonces, si necesitamos resolver el mismo problema más tarde, podemos obtener la solución de la lista de soluciones calculadas y reutilizarla. Este acercamiento al problema se llama memoización (no confundir con memorización; en inglés es llamado memoization, véase en). Si estamos seguros de que no volveremos a necesitar una solución en concreto, la podemos descartar para ahorrar espacio.

Comenzando con el pseudocódigo:
FUNC Fibonacci (↓n: NATURAL): NATURAL
VARIABLES
tabla: ARRAY [0..n] DE NATURALES
i: NATURAL
INICIO
SI n = 0 ENTONCES
DEVOLVER 0
SINOSI n = 1 ENTONCES
DEVOLVER 1
SINO
tabla[0] := 0
tabla[1] := 1
PARA i = 2 HASTA n HACER
tabla[i] := tabla[i-1] + tabla[i-2]
FINPARA
DEVOLVER tabla[n]
FINSI
FIN

Una implementación en C de este algoritmo es la siguiente (he cambiado los tipos de dato para que se puedan calcular valores más grandes):
long long fibonacciArreglo(int n) {
long long * lista;
if(n == 0) return 0;
if(n == 1) return 1;
lista = (long long *) malloc(sizeof(long long) * n);
lista[0] = 0;
lista[1] = 1;
int i;
for (i = 2; i <= n; i++) {
lista[i] = lista[i-1] + lista[i-2];
}
return lista[n];
}
Como podemos ver, se reservan n espacios de memoria para almacenar todos los valores de la sucesión, aunque solamente se emplean dos de estos valores a la hora de cálcular el siguente número. Lo cual deriva en un "desperdicio" de memoria. 

Es por eso que basándome en el enunciado que está más arriba en negritas, pensé que implementar una cola sería una opción para ahorrar espacio, podemos agregar dinámicamente los nuevos valores y quitar los que ya no usaremos, de tal manera solo tendríamos en memoria los valores necesarios para calcular el siguiente número en la sucesión. El código queda como sigue:
long long fibonacciCola(int n) {
Cola cola;
if (n == 0) return 0;
if (n == 1) return 1;
creaLista(&cola);
formar(&cola, 0);
formar(&cola, 1);
int i;
for (i = 2; i <= n; i++) {
// Quitamos el elemento menos reciente
long long fi1 = atender(&cola);
// Obtenemos el valor del siguiente más reciente
long long fi2 = valorPrincipio(&cola);
// Almacenamos el resultado en la cola
formar(&cola, fi1 + fi2);
}
return valorFinal(&cola);
}
De esta forma aseguramos que para cualquier tamaño de n solo se ocupen a lo más 3 espacios de memoria.

El código está disponible en la sección de extras del proyecto analizando-algo

¡Saludos!
@fferegrino :)

Invertir una cadena [C#]

Durante la plática de reclutamiento para Microsoft que hubo hace unos días en la ESCOM hicieron una prueba a los asistentes, la prueba consistía en escribir el código de dos funciones:
Una capaz de invertir una cadena, es decir, pasar de "this is a string" a "gnirts a si siht"
Otra muy similar pero solo debía invertir las palabras, pasando de "this is a string" a "siht si gnirts"
Basándonos en un prototipo de función más o menos así:
char* reverse(const char* str)

El lenguaje a usar era cualquiera con el que te sintieras cómodo, C#, C, Java... en fin. La idea era que usaras la menor cantidad de herramientas provistas por el framework o por el lenguaje que escogiste (¡no se valía el .Reverse()!), es decir que todo lo hicieras "artesanalmente". Eso si, no recuerdo muy bien cuanto tiempo dieron para escribir.

Por suerte yo me había encontrado con un problema similar unos días antes, así que creo que no me fue tan mal en esta. Mi solución la propuse en dos lenguajes, C para la primera y (por cuestiones de tiempo) C# para la segunda. Ahora vengo acá a colocar mis soluciones un poco más pensadas y totalmente funcionales escritas en C#:

using System;
using System.Linq;
using System.Text;

namespace ReverseStringsMsft
{
class Program
{
static void Main(string[] args)
{
string s = "this is a string";
string res = Reverse(s);
string resWord = ReverseWords(s);
Console.WriteLine(s);
Console.WriteLine(res);
Console.WriteLine(resWord);
Console.Read();
}

/// <summary>
/// Invierte una cadena
/// </summary>
/// <param name="str">La cadena que será invertida</param>
/// <returns>Una nueva instancia de String</returns>
public static string Reverse(string str)
{
if (str == null)
return null;
// Convertir a un arreglo
char[] cr = str.ToArray();
// Llamamos a la funcion para toda la cadena
Reverse(cr, 0, cr.Length - 1);
return new string(cr);
}

/// <summary>
/// Invierte las subcadenas (separadas por espacios) contenidas dentro de una cadena
/// </summary>
/// <param name="str">La cadena que será invertida</param>
/// <returns>Una nueva instancia de String</returns>
public static string ReverseWords(string str)
{
if (str == null)
return null;
// Convertir a un arreglo
char[] cr = str.ToArray();
int wordStart = 0;
int end;
// Recorremos la cadena para encontrar espacios
for (int i = 0; i < cr.Length; i++)
{
char c = cr[i];
// Por cada espacio o cada vez que lleguemos al final de la cadena
// llamaremos a la funcion especialmente para la ubicacion de
// la palabra encontrada
if (c == ' ' || i == cr.Length - 1)
{
end = i - 1;
Reverse(cr, wordStart, end);
wordStart = i + 1;
}
}
return new string(cr);
}

/// <summary>
/// Cambia de posición los caracteres desde <paramref name="start"/> a
/// <paramref name="end"/> hasta que todos estén invertidos
/// </summary>
/// <param name="str">El arreglo a ser invertido</param>
/// <param name="start">Inicio</param>
/// <param name="end">Final</param>
private static void Reverse(char[] str, int start, int end)
{
for (; start < end; start++, end--)
{
// Cambiamos uno a uno los caracteres desde la posicion
// inicial hasta la final, y aumentamos las variables para irlos
// intercambiando como deseamos
char aux = str[start];
str[start] = str[end];
str[end] = aux;
}
}

}
}


¡Saludos!
@fferegrino :)