2012 - Prefixuffix
Explicación
Nos dan un string de longitud . Debemos elegir un prefijo y un sufijo de la misma longitud tales que:
- Sean cíclicamente equivalentes.
- No se solapen en el string original.
- Su longitud sea máxima.
Si no existiera la condición de equivalencia cíclica, el problema se reduciría a hallar el prefijo más largo que también es un sufijo sin solaparse. Esta es una aplicación clásica de la función prefijo de KMP.
Sin embargo, como se permite la equivalencia cíclica, el prefijo y el sufijo pueden coincidir después de una rotación, así que debemos detectar esas coincidencias de forma eficiente. Esto se resuelve usando hash rolling para comparar subcadenas rápidamente.
Visualizar el requisito
Podemos pensar en la estructura del string de la siguiente forma:
Index: 0 i n-i-L n-i n
String: |---U---|---V---| . . . . . |---V---|---U---|
Length: i L L iAquí:
- es el prefijo que debe coincidir con el sufijo.
- es un segmento del medio que aparece a ambos lados del centro.
- El prefijo y el sufijo no deben solaparse, así que .
- Las partes del medio también deben coincidir para satisfacer la condición de rotación cíclica.
Así la longitud candidata final queda .
Idea del algoritmo
Primero computamos la función prefijo (pi) usando KMP. Esto da todas las longitudes posibles de bordes prefijo = sufijo del string.
A continuación debemos tratar la parte de rotación cíclica. Para esto computamos un arreglo auxiliar best[i] que guarda la longitud máxima tal que:
- la subcadena
- coincide con la subcadena
Esta comparación debe ser rápida porque ocurre muchas veces. Por eso usamos hash rolling para que cada comparación de subcadenas tome tiempo .
Llenamos best[i] de derecha a izquierda:
- Empezamos con
best[i] = best[i+1] + 2. - Lo reducimos hasta que las dos subcadenas coincidan y los segmentos no se solapen.
Por último:
- Empezamos con
ans = best[0]. - Iteramos sobre todas las longitudes de borde usando la función prefijo.
- Si una longitud de borde cumple , actualizamos .
Esto combina efectivamente:
- KMP para enumerar candidatos válidos prefijo–sufijo.
- Hash rolling para verificar la estructura de rotación cíclica de forma eficiente.
El resultado es la longitud máxima válida que satisface todas las condiciones.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
const long long BASE = 31;
const long long MOD = 998244353;
// BeginCodeSnip{KMP and Rolling Hash}
long long normalize(long long x) {
x %= MOD;
if (x < 0) x += MOD;
return x;
}
vector<int> prefix_function(const string &s) {
int n = s.size();
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) { j = pi[j - 1]; }
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
struct RollingHash {
vector<long long> pref;
vector<long long> power;
RollingHash(const string &s) {
int n = s.size();
pref.assign(n + 1, 0);
power.assign(n + 1, 1);
for (int i = 0; i < n; i++) {
pref[i + 1] = normalize(pref[i] * BASE + s[i]);
power[i + 1] = normalize(power[i] * BASE);
}
}
long long get(int l, int r) {
return normalize(pref[r + 1] - pref[l] * power[r - l + 1]);
}
};
// EndCodeSnip
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
string s;
cin >> n >> s;
RollingHash hasher(s);
vector<int> pi = prefix_function(s);
vector<int> best(n);
for (int i = n / 2 - 1; i >= 0; --i) {
best[i] = best[i + 1] + 2;
while (best[i] > 0) {
int len = best[i];
if (i + len - 1 >= n - i - len ||
hasher.get(i, i + len - 1) != hasher.get(n - i - len, n - 1 - i))
best[i]--;
else break;
}
}
int ans = best[0];
for (int i = pi[n - 1]; i > 0; i = pi[i - 1])
if (i * 2 <= n) ans = max(ans, i + best[i]);
cout << ans << '\n';
return 0;
}