1047 - Visible Lattice Points

por
Edit
Link al problema1047 - Visible Lattice Points

Dificultad: Fácil

Problema:
Un punto lattice $(x, y)$ en el primer cuadrante ($x, y0$ son números enteros mayores o iguales a 0), que no sea el origen, es visible desde el origen si la línea de $(0, 0)$ a $(x, y)$ no pasa a través de cualquier otro punto lattice. Escriba un programa que, dado un valor para el tamaño $N$ ($1 ≤ N ≤ 1000$), calcula el número de puntos visibles $(x, y)$ con $0 <= x, y <= N$.

Solución:
Para resolver este problema trazaremos las rectas (imaginariamente) desde todos los puntos que pueden existir en la cuadrícula hacia el origen. Dichas rectas, además de pasar siempre por el origen, la coordenada $(x_2 = 0, y_2 = 0)$, también deberán pasar por el punto $(x_1 = x, y_2 = y)$ donde $0 ≤ x, y ≤ N$. Recordemos la ecuación de la recta: $(x_2 - x_1) / (y_2 - y_1) + b$, por tanto para nuestras rectas la ecuación se reducirá a $x / y$.
Podemos ver que algo sucede con los puntos que no son visibles, y es que la recta que los une puede ser expresada por un múltiplo de la recta de uno que si es visible. El caso más sencillo: tomando el punto visible $(1, 1)$ y el punto no visible $(3, 3)$, tenemos que las coordenadas del punto no visible es tan solo $(3 * 1) / (3 * 1)$, la forma para identificar esto es obtener el MCD de $x$ y $y$, mientras que para el punto visible $MCD(x, y) = 1$, para el no visible $MCD(x, y) > 1$.

Ahora solo hay que obtener todos los puntos dentro de nuestra cuadrícula cuyo MCD sea igual a 1 para cada consulta que nos hagan. Esto es sencillo pero es un gasto inecesario de recursos, por lo que lo mejor es realizar un precálculo partiendo de que para $N = 0$ la respuesta es $0$ y para $N = 1$ la respuesta es $3$. Otro punto a considerar es que podemos solo calcular la mitad de los puntos ya que los puntos son simétricos.

Complejidad: $O(N)$ donde $N$ es el tamaño máximo de nuestra cuadrícula

Código (C++):
Definiremos un arreglo a donde a[i] será la cantidad de puntos visibles para el número i-ésimo dado.

Precálculo:
a[0] = 0;
a[1] = 3;
for(int x = 2; x <= 1000; x++){
 int sum = 0;
 for(int y = 1; y < x; y++){
  if(mcd(x,y) == 1) sum++;
 }
 a[x] = a[x-1] + 2 * sum;
}

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

Chef and Digits (ADIGIT - CodeChef)

por
Edit
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;
}