Template
Sea el string.
Pista 1
Una plantilla de es a la vez un prefijo y un sufijo de . Esto sugiere que deberíamos usar KMP.
Pista 2
Si es una plantilla de y es una plantilla de , entonces también es una plantilla de .
Demostración
Como es una plantilla de , podemos construir usando . Como es una plantilla de , podemos construir usando .
Por lo tanto, también podemos construir usando (es decir, es una plantilla de ).
Pista 3
Primero, usamos KMP para computar la función prefijo de . La plantilla más corta de es o bien mismo o la plantilla más corta de .
Demostración
En su lugar demostraremos que cualquier plantilla de que no es mismo también es una plantilla de , ya que es equivalente.
Como es el prefijo propio más largo de que también es un sufijo, sabemos que el único string más largo que puede ser plantilla de es mismo. Sea alguna plantilla de que no es mismo.
A continuación, afirmamos que podemos construir al menos la mitad del prefijo de usando . Si , entonces hemos terminado. En caso contrario, en el proceso de construir usando de izquierda a derecha, debe haber algún punto en el que pasamos de (construido menos de la mitad de ) a (construido al menos la mitad pero no más que ).
De forma similar, podemos construir al menos la mitad del sufijo de usando . Luego podemos combinar estos dos fragmentos para construir mismo, y por tanto también es una plantilla de .
Pista 4
¡No hay que tener miedo de usar KMP otra vez!
Solución 1
Complejidad temporal: .
Primero computamos la función prefijo de usando KMP.
Si , entonces nunca será la plantilla más corta de porque sabemos que es una plantilla de .
¡Esto significa que solo tenemos plantillas candidatas que comprobar! Luego podemos usar KMP de nuevo sobre cada candidata y verificar si el “hueco” máximo entre coincidencias es suficientemente pequeño.
#include <bits/stdc++.h>
using namespace std;
string s;
int p[500000];
int shortest_template(int n) {
if (!p[n - 1]) return n;
if (p[n - 1] * 2 >= n) return shortest_template(p[n - 1]);
int cand = shortest_template(p[n - 1]);
for (int i = 0, j = 0, curr = -1; i < n; i++) {
while (j && (j == cand || s[i] != s[j])) j = p[j - 1];
if (s[i] == s[j]) j++;
if (j == cand) curr = i;
if (i - curr >= cand) return n;
}
return cand;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> s;
int n = s.size();
for (int i = 1; i < n; i++) {
p[i] = p[i - 1];
while (p[i] && s[i] != s[p[i]]) p[i] = p[p[i] - 1];
if (s[i] == s[p[i]]) p[i]++;
}
cout << shortest_template(n);
return 0;
}Solución 2
Complejidad temporal: .
Primero computamos la función prefijo de usando KMP. Mientras hacemos esto, también construimos un árbol donde el padre del nodo es para cada .
Observemos que el subárbol del nodo es el conjunto de todas las posiciones donde .
Esto significa que podemos recorrer el árbol hacia arriba desde el nodo y llevar la cuenta de los nodos en el subárbol del nodo actual (usando una lista enlazada).
Para comprobar si el nodo actual funciona, simplemente verificamos si los huecos entre posiciones en el subárbol son suficientemente pequeños. Podemos llevar la cuenta de estos huecos manteniendo un máximo global que actualizamos cada vez que borramos un nodo de la lista enlazada.
#include <bits/stdc++.h>
using namespace std;
const int MX = 5e5 + 5;
string s;
int p[MX], mx_gap = 1, ans, prv[MX], nxt[MX];
int graph[MX];
bool visited[MX];
int num_child[MX];
int n;
void dfs(int node) {
assert(node <= n);
visited[node] = true;
if (node && !visited[p[node - 1]]) {
dfs(p[node - 1]);
if (mx_gap <= node) ans = min(ans, node);
}
mx_gap = max(mx_gap, nxt[node] - prv[node]);
nxt[prv[node]] = nxt[node];
prv[nxt[node]] = prv[node];
for (int ind = num_child[node]; ind < num_child[node + 1]; ++ind) {
int i = graph[ind];
if (!visited[i]) dfs(i);
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> s;
n = s.size();
++num_child[0];
for (int i = 1; i < n; i++) {
p[i] = p[i - 1];
while (p[i] && s[i] != s[p[i]]) p[i] = p[p[i] - 1];
if (s[i] == s[p[i]]) p[i]++;
++num_child[p[i]];
}
for (int i = 1; i <= n + 1; ++i) num_child[i] += num_child[i - 1];
graph[--num_child[0]] = 1;
for (int i = 1; i < n; i++) graph[--num_child[p[i]]] = i + 1;
prv[0] = 0, nxt[0] = 1, prv[n] = n - 1, nxt[n] = n;
for (int i = 1; i < n; i++) prv[i] = i - 1, nxt[i] = i + 1;
ans = n;
dfs(n);
cout << ans;
return 0;
}Solución 3
Complejidad temporal: .
Primero computamos la función prefijo de usando KMP.
Luego resolvemos el siguiente problema de forma recursiva: dado un string, ¿cuál es la longitud de su plantilla más corta? Podemos hacerlo así:
- Hallar la respuesta para el prefijo propio más largo del string que también es un sufijo.
- Comprobar si esta respuesta también funciona para el string actual.
- Si lo hace, devolver esa longitud; si no, devolver la longitud del string actual.
Resolveremos cada paso 2 usando DP y otro KMP en tiempo amortizado .
Si sabemos dónde coincide una plantilla candidata en un string dado, entonces también podemos comprobar si es una plantilla en tiempo lineal. Sin embargo, esto correrá en tiempo cuadrático en total si no tenemos cuidado.
La observación clave es que, como sabemos que la plantilla candidata también es una plantilla del prefijo propio más largo del string dado, solo necesitamos hacer la DP sobre la parte restante del string. ¡Esto significa que solo necesitamos procesar cada carácter de en la DP a lo sumo una vez!
Para hallar las coincidencias con la misma eficiencia, ejecutamos KMP sobre el mismo string parcial en cada paso recursivo.
#include <bits/stdc++.h>
using namespace std;
string s;
int p[500000];
int shortest_template(int n) {
if (!p[n - 1]) return n;
int cand = shortest_template(p[n - 1]);
for (int i = p[n - 1], j = cand, curr = p[n - 1] - 1; i < n; i++) {
while (j && (j == cand || s[i] != s[j])) j = p[j - 1];
if (s[i] == s[j]) j++;
if (j == cand) curr = i;
if (i - curr >= cand) return n;
}
return cand;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> s;
int n = s.size();
for (int i = 1; i < n; i++) {
p[i] = p[i - 1];
while (p[i] && s[i] != s[p[i]]) p[i] = p[p[i] - 1];
if (s[i] == s[p[i]]) p[i]++;
}
cout << shortest_template(n);
return 0;
}