题面
解法
花了好久终于把这道题弄懂了……
- 可以发现,满足性质的字符串一定同时是整个串的前缀和后缀
- 首先考虑这样一个时间复杂度为 O ( n 2 ) O(n^2) O(n2)的做法:
- 对原串进行KMP求出 n x t nxt nxt数组,那么我们就可以一个一个枚举同时为前缀和后缀的字符串
- 考虑如何检验答案的合法性,找出该字符串在原串中出现的位置(这里指匹配到的最后一位),将这些位置标记一下。如果相邻标记点的最大距离不超过该字符串的长度,那么这个字符串是一定可以覆盖原串,即为一个合法解
- 时间复杂度为 O ( n 2 ) O(n^2) O(n2),无法通过此题
- 接下来考虑如何优化这个过程
- 可以发现,假设有两个前缀 p r e ( i ) , p r e ( j ) ( i < j ) pre(i),pre(j)\ \ (i<j) pre(i),pre(j) (i<j),那么如果这个位置可以匹配到 p r e ( j ) pre(j) pre(j), p r e ( i ) pre(i) pre(i)也一定可以被匹配到,反之就不一定是正确的了
- 那么,如果我们按照从小到大的顺序枚举答案,就会发现匹配的点只会被一个一个地删去,并不会加入一些点
- 然后发现原来的问题就转化成下面的新问题:如何在到较长答案的时候更新所有匹配点
- 考虑这样一个做法:建出 f a i l fail fail树,即连接 ( n x t [ i ] , i ) (nxt[i],i) (nxt[i],i),这样显然会形成一棵树。可能满足要求的点一定在 0 0 0到 n n n的这一条路径上,假设我们现在做到点 i i i,那么匹配的点一定只可能在 i i i的子树中,只要删除不属于这条路径的 i i i的子树,然后继续做即可
- 现在大致证明一下为什么匹配的点一定只可能在 i i i的子树中
- 假设 j j j这个点和 p r e ( i ) pre(i) pre(i)匹配,这里指的是匹配时 p r e ( i ) pre(i) pre(i)的最后一个位置在 j j j上。那么可以发现 p r e ( i ) pre(i) pre(i)既是 p r e ( j ) pre(j) pre(j)的前缀,同时也是 p r e ( j ) pre(j) pre(j)的后缀,所以 i i i一定在 j j j到根的路径上,反过来就说明了 i i i为 j j j的祖先, j j j在 i i i的子树中
- 如果 j j j这个点和 i i i不匹配,那么它一定不在 i i i的子树里,只要删去这个点就可以了
- 那么,这个算法的正确性已经证明完毕
- 如何维护删除操作呢?只要使用一个双向链表,在删除的时候顺便维护一下匹配点之间距离的最大值就可以了
- 因为每一个位置只会被删除至多一次,所以时间复杂度: O ( ∣ S ∣ ) O(|S|) O(∣S∣)
代码
#include <bits/stdc++.h>
#define N 500010
using namespace std;
struct Edge {
int next, num;
} e[N * 3];
int n, mx, ans, cnt, pre[N], nxt[N], used[N], fail[N];
char st[N];
void add(int x, int y) {
e[++cnt] = (Edge) {e[x].next, y};
e[x].next = cnt;
}
void kmp() {
fail[1] = 0;
for (int i = 2; i <= n; i++) {
int j = fail[i - 1];
while (j && st[j + 1] != st[i]) j = fail[j];
if (st[j + 1] == st[i]) j++; fail[i] = j;
}
}
void del(int x) {
pre[nxt[x]] = pre[x], nxt[pre[x]] = nxt[x];
mx = max(mx, nxt[x] - pre[x]);
for (int p = e[x].next; p; p = e[p].next)
del(e[p].num);
}
void dfs(int x) {
if (mx <= x) return ans = x, void();
int nxt = 0;
for (int p = e[x].next; p; p = e[p].next) {
int k = e[p].num;
if (used[k]) nxt = k;
else del(k);
}
dfs(nxt);
}
int main() {
scanf(" %s", st + 1);
n = strlen(st + 1); cnt = n; kmp();
for (int i = 1; i <= n; i++)
add(fail[i], i), pre[i] = i - 1, nxt[i] = i + 1;
nxt[n] = 0, mx = 1;
for (int i = n; i; i = fail[i]) used[i] = 1;
dfs(0); cout << ans << "\n";
return 0;
}