Problemas de Programación Competitiva: Análisis de Intervalos Especiales y Configuraciones Mágicas

Planteamiento del Problema

Sea una secuencia de valores \\(V_1, V_2, \dots, V_N\\). Definimos el valor de un intervalo \\([l, r]\\) (donde \\(l < r\\)) como:

Estrategia de Solución

Observamos que el valor máximo se alcanza cuando \\(V_i\\) es el máximo del intervalo, \\(V_j\\) es el mínimo, y \\(V_k \oplus V_m\\) es el mínimo posible. Para encontrar el mínimo XOR entre dos elementos de un conjunto, basta considerar elementos adyacentes después de ordenar el conjunto.

Mantenemos dos estructuras: una para los valores ordenados del intervalo actual y otra para los valores XOR entre elementos consecutivos. Utilizando la técnica de dos punteros, podemos contar eficientemente cuántos intervalos tienen valor mayor o igual a un umbral \\(x\\). La respuesta para cada consulta se obtiane por diferencia de prefijos.

Complejidad espacial: \\(O(N)\\)
Complejidad temporal: \\(O(QN\log N)\\)

Implementación

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;

const int TAM = 50001;

i64 valores[TAM];
multiset<i64> ordenados, xorAdyacentes;

void agregar(i64 elem) {
  auto it = ordenados.lower_bound(elem);
  if(it != ordenados.begin() && it != ordenados.end()) {
    xorAdyacentes.erase(xorAdyacentes.find((*prev(it)) ^ (*it)));
  }
  if(it != ordenados.begin()) {
    xorAdyacentes.insert((*prev(it)) ^ elem);
  }
  if(it != ordenados.end()) {
    xorAdyacentes.insert((*it) ^ elem);
  }
  ordenados.insert(elem);
}

void eliminar(i64 elem) {
  auto it = ordenados.lower_bound(elem);
  if(it != ordenados.begin() && it != ordenados.end()) {
    xorAdyacentes.insert((*prev(it)) ^ (*it));
  }
  if(it != ordenados.begin()) {
    xorAdyacentes.erase(xorAdyacentes.find((*prev(it)) ^ elem));
  }
  if(it != ordenados.end()) {
    xorAdyacentes.erase(xorAdyacentes.find((*it) ^ elem));
  }
  ordenados.erase(ordenados.find(elem));
}

i64 contarIntervalos(i64 umbral) {
  ordenados.clear(), xorAdyacentes.clear();
  agregar(valores[1]);
  i64 total = 0;
  for(int inicio = 1, fin = 1; inicio <= n; eliminar(valores[inicio]), ++inicio) {
    for(; fin <= n && (xorAdyacentes.empty() || *prev(ordenados.end()) - *ordenados.begin() - *xorAdyacentes.begin() < umbral); ) {
      if(++fin <= n) {
        agregar(valores[fin]);
      }
    }
    total += (n - fin + 1);
  }
  return total;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0);
  int n, q;
  cin >> n >> q;
  for(int i = 1; i <= n; ++i) {
    cin >> valores[i];
  }
  while(q--) {
    i64 limiteInf, limiteSup;
    cin >> limiteInf >> limiteSup;
    cout << contarIntervalos(limiteInf) - contarIntervalos(limiteSup + 1) << "\n";
  }
  return 0;
}


Configuraciones de Dispositivos Mágicos

Planteamiento del Problema

Debemos organizar \\(N\\) dispositivos mágicos en un círculo. Si los dispositivos \\(i\\) y \\(j\\) son adyacentes, forman un canal con valor mágico \\(m_{i,j}\\). Una configuración es estable si la diferencia entre cualquier par de valores de canales no excede \\(k\\). Algunas posiciones ya están predeterminadas. ¿Cuántas configuraciones estables existen?

Estrategia de Solución

Para simplificar, rotamos el círculo para fijar el primer dispositivo. Si no hay dispositivos fijos, asignamos arbitrariamente el dispositivo 1 a la primera posición y multiplicaremos el resultado final por \\(N\\).

Enumeramos el posible valor mínimo \\(x\\) de los canales. Definimos \\(dp[flag, mascara, ultimo]\\) donde:
- \\(flag\\) indica si ya hemos utilizado un canal con valor \\(x\\)
- \\(mascara\\) representa el conjunto de dispositivos ya colocados
- \\(ultimo\\) es el último dispositivo colocado

Esta dimensión \\(flag\\) evita conteos duplicados. La transición entre estados considera adding un nuevo dispositivo que cumpla con las restricciones de estabilidad.

Complejidad espacial: \\(O(N2^N)\\)
Complejidad temporal: \\(O(N^3 2^N)\\)

Implementación

#include<bits/stdc++.h>
using namespace std;

const int MAX_N = 14, MODULO = 998244353;

int n, k;
int dispositivo[MAX_N];
int valoresCanales[MAX_N][MAX_N];
int todosValores[MAX_N * MAX_N / 2], contadorValores;
int dp[2][1 << MAX_N][MAX_N];
int respuesta;

int main() {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> n >> k;
  
  int posicionFija = 0;
  bool tieneFijo = false;
  
  for(int i = 1; i <= n; ++i) {
    cin >> dispositivo[i];
    if(dispositivo[i]) {
      tieneFijo = true;
      posicionFija = i;
    }
  }
  
  int dispositivosRotados[MAX_N];
  if(tieneFijo) {
    for(int i = 1; i <= n; ++i) {
      int nuevaPos = i >= posicionFija ? i - posicionFija + 1 : n - posicionFija + 1 + i;
      dispositivosRotados[nuevaPos] = dispositivo[i];
    }
  } else {
    dispositivosRotados[1] = 1;
  }
  
  for(int i = 1; i <= n; ++i) {
    for(int j = 1; j <= n; ++j) {
      cin >> valoresCanales[i][j];
      if(j < i) {
        todosValores[contadorValores++] = valoresCanales[i][j];
      }
    }
  }
  
  sort(todosValores, todosValores + contadorValores);
  
  for(int idxValor = 0; idxValor < contadorValores; ++idxValor) {
    int valorMin = todosValores[idxValor];
    
    for(int estado = 0; estado < 2; ++estado) {
      for(int mascara = 0; mascara < (1 << n); ++mascara) {
        fill(dp[estado][mascara], dp[estado][mascara] + n, 0);
      }
    }
    
    dp[0][1 << (dispositivosRotados[1] - 1)][dispositivosRotados[1]] = 1;
    
    for(int mascara = 1; mascara < (1 << n); ++mascara) {
      for(int estado = 0; estado < 2; ++estado) {
        int count = __builtin_popcount(mascara);
        for(int actual = 1; actual <= n; ++actual) {
          if(!dp[estado][mascara][actual]) continue;
          
          for(int siguiente = 1; siguiente <= n; ++siguiente) {
            if((mascara >> (siguiente - 1)) & 1) continue;
            if(dispositivosRotados[count + 1] && dispositivosRotados[count + 1] != siguiente) continue;
            
            int valorCanal = valoresCanales[actual][siguiente];
            if(valorCanal < valorMin || valorCanal - valorMin > k) continue;
            
            int nuevoEstado = estado | (valorCanal == valorMin);
            int nuevaMascara = mascara | (1 << (siguiente - 1));
            dp[nuevoEstado][nuevaMascara][siguiente] = 
              (dp[nuevoEstado][nuevaMascara][siguiente] + dp[estado][mascara][actual]) % MODULO;
          }
        }
      }
    }
    
    int mascaraCompleta = (1 << n) - 1;
    for(int ultimo = 1; ultimo <= n; ++ultimo) {
      int valorCanalCierre = valoresCanales[ultimo][dispositivosRotados[1]];
      if(valorCanalCierre < valorMin || valorCanalCierre - valorMin > k) continue;
      
      respuesta = (respuesta + dp[1][mascaraCompleta][ultimo]) % MODULO;
      if(valorCanalCierre == valorMin) {
        respuesta = (respuesta + dp[0][mascaraCompleta][ultimo]) % MODULO;
      }
    }
  }
  
  if(!tieneFijo) {
    respuesta = (i64)respuesta * n % MODULO;
  }
  
  cout << respuesta;
  return 0;
}

Etiquetas: algoritmos programación competitiva multiset dp bitmask

Publicado el 10-2 02:38