Longest k-Good Segment
Solución 1
Explicación
Podemos hacer búsqueda binaria del mayor tamaño de segmento válido. Si este segmento es
válido, actualizamos el valor actual de con nuestros índices y .
En lugar de usar un map, optamos por una
tabla hash de tamaño prefijado para operaciones de consulta
más rápidas en .
Implementación
Complejidad temporal:
// CodeSnip{CPP Short Template}
// BeginCodeSnip{HashTable}
/**
* Descripción: Mapa hash con la misma API que unordered\_map, pero \tilde 3x
* más rápido. La capacidad inicial debe ser una potencia de 2 si se indica. Fuente: KACTL
* Uso: ht<int,int> h({},{},{},{},{1<<16});
*/
mt19937 rng((uint32_t)chrono::steady_clock::now().time_since_epoch().count());
const long double PI = acos((long double)-1);
#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
struct chash { /// usar la mayoría de los bits, no solo los de menor peso
const uint64_t C = ll(2e18 * PI) + 71; // impar grande
const int RANDOM = rng();
ll
operator()(ll x) const { /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
return __builtin_bswap64((x ^ RANDOM) * C);
}
};
template <class K, class V> using um = unordered_map<K, V, chash>;
template <class K, class V> using ht = gp_hash_table<K, V, chash>;
template <class K, class V> V get(ht<K, V> &u, K x) {
auto it = u.find(x);
return it == end(u) ? 0 : it->s;
}
// EndCodeSnip{HashTable}
const int mx = 5e5 + 1;
int n, k;
int v[mx];
pi ret;
int lowest = 0;
bool check(int x) {
if (x > n) return 0;
ht<int, int> s({}, {}, {}, {}, {1 << 19});
int j = 0;
s[v[0]]++;
for (int i = 0; i < n; i++) {
// construye una ventana deslizante de tamaño x
while (j < n - 1 && j < i + x - 1) {
j++;
s[v[j]]++;
}
// comprueba si hay una ventana nueva
if (sz(s) <= k && j - i == x - 1) {
if (x > lowest) { ret = {i, j}; }
return 1;
}
s[v[i]]--;
if (s[v[i]] <= 0) s.erase(v[i]);
}
return 0;
}
int main() {
setIO();
cin >> n >> k;
for (int i = 0; i < n; i++) cin >> v[i];
int lo = 1, hi = n;
lo--;
assert(lo <= hi); // asumiendo que f es decreciente
while (lo < hi) { // primer índice tal que f es verdadero
int mid = lo + (hi - lo + 1) / 2;
check(mid) ? lo = mid : hi = mid - 1;
}
cout << ret.f + 1 << " " << ret.s + 1;
}Solución 2
Explicación
En lugar de buscar por binaria el tamaño máximo, podemos mantener una ventana deslizante de a . Solo incrementamos esta ventana hacia la derecha si se cumplen dos condiciones:
- , es decir, que no nos salimos de los límites.
- Añadir a la ventana no aumenta la cantidad de valores distintos a más de .
Seguimos expandiendo hacia la derecha hasta que alguna de estas condiciones deje de cumplirse. Si nuestro segmento nuevo es más grande que el anterior, actualizamos el valor del segmento más grande.
Finalmente, incrementamos , quitamos una ocurrencia de de la lista de frecuencias y actualizamos el número de valores distintos.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 9, MAX_Ai = 1e6 + 9;
int n, k;
int a[N];
int frequency[MAX_Ai]; // frecuencia de cada número en la ventana
int maxSegment, leftMaxSegment,
rightMaxSegment; // tamaño máximo del segmento y sus extremos
int main() {
scanf("%d%d", &n,
&k); // entrada grande: usar scanf/printf en lugar de cin/cout
for (int i = 0; i < n; i++) scanf("%d", a + i);
int r = -1; // el extremo derecho de la ventana actual
int cntDifVal = 0; // el número de valores distintos en la ventana
// si frequency[val] == 0 entonces añadir val a la ventana resultará en
// añadir un elemento distinto nuevo, lo que aumenta el número de valores
// distintos en 1
for (int l = 0; l < n; l++) { // l es el extremo izquierdo de la ventana
/*
podemos expandir la ventana [l,r] a [l,r+1] si se cumplen estas 2 condiciones:
1. r < n-1, para no salir de los límites del arreglo
2. añadir el valor a[r+1] a la ventana no aumenta el
número de valores distintos en la ventana a más de k
*/
while (r < n - 1 && cntDifVal + (frequency[a[r + 1]] == 0) <= k) {
cntDifVal +=
frequency[a[r + 1]] == 0; // actualizar el número de valores distintos
frequency[a[r + 1]]++; // actualizar el arreglo de frecuencias
r++; // actualizar el extremo derecho de la ventana actual
}
// actualizar la respuesta si encontramos un segmento más grande
if (r - l + 1 > maxSegment) {
maxSegment = r - l + 1;
leftMaxSegment = l;
rightMaxSegment = r;
}
// aquí descartamos el primer elemento a[l] y el segmento actual
// queda [l+1,r]
frequency[a[l]]--; // actualizar la frecuencia
cntDifVal -= frequency[a[l]] == 0; // actualizar el número de valores distintos
}
// sumamos uno porque la respuesta debe estar indexada desde uno
printf("%d %d\n", leftMaxSegment + 1, rightMaxSegment + 1);
}import java.io.*;
import java.util.*;
public class KGood {
// CodeSnip{Kattio}
public static void main(String[] args) throws IOException {
Kattio io = new Kattio();
int N = io.nextInt();
int K = io.nextInt();
int difVal = 0; // Número de valores distintos en la ventana.
int left = 0;
int maxLen = 0;
int maxL = 0;
int maxR = 0;
int[] arr = new int[N];
for (int i = 0; i < N; i++) { arr[i] = io.nextInt(); }
int max = Arrays.stream(arr).max().getAsInt();
int[] freq = new int[max + 1]; // Frecuencia de cada número en la ventana.
for (int i = 0; i < N; i++) {
int x = arr[i];
// Incrementar la frecuencia del número.
if (freq[x] == 0) {
freq[x] = 1;
difVal++;
} else {
freq[x]++;
}
// Si la ventana no es válida, desplazamos el extremo izquierdo hacia la derecha.
while (difVal > K) {
freq[arr[left]]--;
if (freq[arr[left]] == 0) { difVal--; }
left++;
}
// Actualizar la longitud de la ventana.
int curLen = i - left + 1;
if (curLen > maxLen) {
maxLen = curLen;
maxL = left;
maxR = i;
}
}
io.printf("%d %d%n", maxL + 1, maxR + 1);
io.close();
}
}