Límite de tiempo por prueba: 5 segundos
Límite de memoria por prueba: 512 megabytesSe le da una cadena
s
de longitudn
(n
≤ 5000). Puede seleccionar cualquier prefijo apropiado de esta cadena que también sea su sufijo y eliminar el prefijo seleccionado o el sufijo correspondiente. Luego puede aplicar una operación análoga a una cadena resultante y así sucesivamente. ¿Cuál es la longitud mínima de la cadena final que se puede lograr después de aplicar la secuencia óptima de tales operaciones?Entrada
La primera línea de cada prueba contiene una cadenas
que consta de pequeñas letras en inglés.Salida
Genera un solo entero: la longitud mínima de la cadena final, que se puede lograr después de aplicar la secuencia óptima de tales operaciones.Ejemplos
+-------+--------+----------------------------------+ | Input | Output | Explanation | +-------+--------+----------------------------------+ | caaca | 2 | caaca → ca|aca → aca → ac|a → ac | +-------+--------+----------------------------------+ | aabaa | 2 | aaba|a → a|aba → ab|a → ab | +-------+--------+----------------------------------+ | abc | 3 | No operations are possible | +-------+--------+----------------------------------+
Esto es lo que he logrado hacer hasta ahora:
Calcule la función de prefijo para todas las subcadenas de una cadena dada en O (n ^ 2)
Verifique el resultado de realizar todas las combinaciones posibles de operaciones en O (n ^ 3)
Mi solución pasa todas las pruebas a n
≤ 2000 pero excede el límite de tiempo cuando 2000 < n
≤ 5000. Aquí está su código:
#include <iostream>
#include <string>
using namespace std;
const int MAX_N = 5000;
int result; // 1 less than actual
// [x][y] corresponds to substring that starts at position `x` and ends at position `x + y` =>
// => corresponding substring length is `y + 1`
int lps[MAX_N][MAX_N]; // prefix function for the substring s[x..x+y]
bool checked[MAX_N][MAX_N]; // whether substring s[x..x+y] is processed by check function
// length is 1 less than actual
void check(int start, int length) {
checked[start][length] = true;
if (length < result) {
if (length == 0) {
cout << 1; // actual length = length + 1 = 0 + 1 = 1
exit(0); // 1 is the minimum possible result
}
result = length;
}
// iteration over all proper prefixes that are also suffixes
// i - current prefix length
for (int i = lps[start][length]; i != 0; i = lps[start][i - 1]) {
int newLength = length - i;
int newStart = start + i;
if (!checked[start][newLength])
check(start, newLength);
if (!checked[newStart][newLength])
check(newStart, newLength);
}
}
int main()
{
string str;
cin >> str;
int n = str.length();
// lps calculation runs in O(n^2)
for (int l = 0; l < n; l++) {
int subLength = n - l;
lps[l][0] = 0;
checked[l][0] = false;
for (int i = 1; i < subLength; ++i) {
int j = lps[l][i - 1];
while (j > 0 && str[i + l] != str[j + l])
j = lps[l][j - 1];
if (str[i + l] == str[j + l]) j++;
lps[l][i] = j;
checked[l][i] = false;
}
}
result = n - 1;
// checking all possible operations combinations in O(n^3)
check(0, n - 1);
cout << result + 1;
}
P: ¿Hay alguna solución más eficiente?
fuente
Respuestas:
Aquí hay una forma de obtener el factor de registro. Sea
dp[i][j]
verdad si podemos alcanzar la subcadenas[i..j]
. Entonces:Ahora itera desde afuera en:
Podemos calcular previamente el partido más largo de cada par a partir
(i, j)
deO(n^2)
la recurrencia,Esto nos permitiría verificar una coincidencia de subcadena que comience en los índices
i
yj
enO(1)
. (Necesitamos ambas direcciones, derecha e izquierda).Cómo obtener el factor de registro
Podemos pensar en una forma de crear una estructura de datos que nos permita determinar si
en
O(log n)
, considerando que ya hemos visto esos valores.Aquí está el código C ++ con árboles de segmentos (para consultas derecha e izquierda, por lo tanto
O(n^2 * log n)
) que incluye el generador de prueba de Bananon. Para 5000 caracteres "a", se ejecutó en 3.54s, 420 MB ( https://ideone.com/EIrhnR ). Para reducir la memoria, uno de los árboles de segmentos se implementa en una sola fila (todavía necesito investigar hacer lo mismo con las consultas del lado izquierdo para reducir aún más la memoria).Y aquí está el código JavaScript (sin la mejora del factor de registro) para mostrar que la recurrencia funciona. (Para obtener el factor de registro, reemplazamos los
k
bucles internos con una consulta de rango único).fuente
i
de la línea 64 que comienza la línea 99 es un poco difícil de entender, ¿es eso intencional? ¿Las declaraciones de bucles en 98 y 99 parecen dejarsei
enMAX_N
el resto del alcance del bucle de la línea 98? (Versión C ++)i
fue solo para el alcance de ese bucle de cuatro líneas, pero podría parecer confuso. Gracias por señalarlo: lo cambié, aunque el cambio no afecta la ejecución del código.