Elevator Code (OmegaUp)

por
Edit

Link al problemaE - Elevator Code

Dificultad: Fácil

Problema:
Dada una cadena $E$, ($|E| ≤ 500$) compuesta por los símbolos #, + y - (La cadena no contendrá más de 7 # juntos) y una cadena de dígitos S, ($|S| =$ número de '#' en $E$). Acomodar cada uno de lo dígitos en $S$ en cada posición # en $E$ para que la operación que representa nos de como resultado el valor máximo.

Para mejor claridad veamos el caso de prueba:
##+##
1234
La mejor forma de acomodarlos para obtener el máximo es $32+41$ lo que nos da $73$ como resultado.

Solución:
Para encontrar el valor máximo de una suma tenemos que maximizar el valor de sus operandos mientras que para las restas tenemos que minimizarlo. Una forma de hacerlo es separar la cadena $E$ en grupos de #, de tal manera que llevemos la cuenta de qué tan grande es el número (cantidad de dígitos) y podamos identificar entre los que son positivos o negativos. Sabiendo esto repartiremos los números en $E$ haciendo que los de mayor valor queden en los grupos positivos con mayor cantidad de dígitos y los de menor valor queden en los negativos con mayor cantidad de dígitos.

Los dígitos se deben repartir en un orden peculiar para que el resultado nos de la solución deseada, y es que primero se deben repartir la cantidad más grande (en el caso del ejemplo anterior son las decenas, números 4 y 3) y luego las unidades. Una vez que se hizo esto se deben repartir a los grupos negativos los dígitos con menor valor siguiendo lo anteriormente dicho.

Después bastara con sumar/restar los valores resultantes de la operación anterior para obtener el resultado.

Complejidad: $O(N log N)$ debido a la ordenación de los dígitos en $E$.

Código (C++):
Para facilitarnos las cosas, definiremos una estructura que contenga la información que necesitamos acerca de los grupos de #.
struct Group{
 int o; // Original index
 int v; // Value of the group  
 int d; // Digit amount
 int s; // Positive or negative: 1 or -1
 int value(){ // Return the final value
  return s * v;
 }
};
Definimos además un arreglo nums de tamaño k donde se almacenarán los dígitos de la cadena $E$ ordenados de manera descendente, un arreglo G de tamaño p de la estructura Grupo donde almacenaremos nuestros grupos ordenado del número positivo con mayor cantidad de dígitos al número negativo con menor cantidad de dígitos.
En el siguiente fragmento de código se reparten los dígitos a los grupos positivos:
  int n = -1;
  int neg;
  int ii = 0;
  int ix = 0;
  n = G[0].d;
  while(n > 0 && G[0].s > 0){
   for(ix = 0; G[ix].d == n && ix <= p; ix++){
    if(G[ix].s < 0) { break; }
    G[ix].d--;
    G[ix].v = (nums[ii] * pow(10,n-1)) + G[ix].v;
    ii++;
   }
   n = G[0].d;
  }
En este se reparten a ls números negativos, no sin antes invertir el orden de los números, dejando el arreglo nums ordenado de manera ascendente:
   reverse(nums+ii, nums+k);
   neg = ix;
   n = G[ix].d;
   while(n){
    for(; G[ix].d == n && ix <= p; ix++){
     G[ix].d--;
     G[ix].v = (nums[ii] * pow(10,n-1)) + G[ix].v;
     ii++;
    }
    n = G[neg].d;
   }

Para finalizar, basta con sumar los valores de nuestro todas nuestras estructuras Grupo para obtener el resultado:
  int res = 0;
  for(i = 0; i <= p; i++){
   res += G[i].value();
  }
  printf("%d\n", res);

Madrigueras (OmegaUp)

por
Edit
Link al problemaMadrigueras

Dificultad: Medio

Problema:
El principal problema radica en encontrar una manera correcta de representar la información que se nos da, la manera más sencilla es representar cada camino hecho por una suricata como una arista unidireccional. Por lo tanto tenemos $N$ ($1 ≤ N ≤ 10^5$) nodos y $M$ aristas entre ellos.

Como ejemplo de lo anterior, veamos cómo representaríamos el caso de prueba de diversas maneras:
Entrada: Representación: Explicación:
5 4
1 2
3 2
4 5
4 5
Las aristas rojas nos indican que la suricata 3 creó un camino hacia el cuarto 2, mientras que la suricata 2 creó un camino hacia el cuarto 1.
Las aristas azules nos indican que la suricata 3 creó un camino hacia el cuarto 2, mientras que la suricata 1 también creó un camino hacia el cuarto 2 y así sucesivamente.

En la imagen anterior también se puede observar algo peculiar, y es que para cada componente conexa solo existen dos posibilidades válidas para colocar las aristas (de acuerdo al problema): Cada componente con $V$ nodos debe tener a lo más $V$ aristas, ya que de tener más implicaría que una suricata construyó más de un camino, lo cual no es posible.

Para responder al problema necesitamos conocer de cuantas maneras se pueden colocar las aristas, dadas las condiciones establecidas anteriormente. Para una componente conexa de $N$ nodos con $N - 1$ aristas (un árbol), podemos elegir cualquiera de los $N$ nodos como la raíz, dejandonos con un total de $N$ maneras de formarlo. Mientras que para una componente con $N$ aristas (un grafo cíclico) solamente tenemos $2$ maneras.

Solución:
Dado que el problema implica componentes conexas, emplearemos la estructura disjoint-set para llevar un registro de ellas. También sabemos que necesitamos conocer la cantidad de nodos y aristas dentro de una misma componente, para lo cual podemos emplear alguna clase de búsqueda en grafos, sin embargo, también podemos modificar las operaciones del disjoint-set para llevar el conteo de los nodos y aristas; la modificación propuesta radica en que a cada llamada a la función unir siempre sumémos una arista a nuestra componente, sin importar si dicha unión representaría un ciclo.

Por último, la respuesta esperada se obtiene de multiplicar las formas de ordenar las aristas para cada componente y aplicarles la operación módulo, ya que el resultado puede ser bastante grande.

Complejidad: $O(M)$, donde $M$ es el número de aristas especificadas en el problema

Código (C++) y pseudocódigo:
Implementación de disjoint-set (C++):
Definiremos un arreglo V, donde V[i] es la cantidad de vértices que tiene la componente i-ésima. Definiremos un arreglo E, donde E[i] es la cantidad de aristas que tiene la componente i-ésima. Definiremos un arreglo G, donde G[i] es el identificador de la componente i-ésima.
// Join function
void join(int i, int j){
 int pi = root(i);
 int pj = root(j);
 E[pi] += 1; // Siempre sumar una arista a nuestra componente
 if(pi != pj){
  G[pj] = G[pi];
  V[pi] += V[pj];
  E[pi] += E[pj];
 }
}

// Root function
int root(int n){
 if(G[n] != n) 
  G[n] = root(G[n]);
 return G[n];
}

Solución al problema (pseudocódigo):
Sea K un arreglo con todos los identificadores de cada componente conexa:
var res := 1
foreach i in K
 if E[i] = V[i]
  res := res * 2
 elsif E[i] = V[i] - 1
  res := res * N
 else
  res := 0
end foreach
print res mod 1000000007