题意:有一个长度为(n)的字符串,求它最长的子串(s)满足(s)是长度为4的倍数的回文串,且它的前半部分和后半部分都是回文串。
(n leq 5 imes 10^5)
首先,显然要用manacher求出以每一位为中心的最长回文串。考虑枚举(s)最中间的一个位置(i),且以其为中心的最长回文串长度为(2 r_i),那么,前半部分的中心(j)就必须在([i - lfloor frac {r_i} {2} floor,i))的区间上,并且(i - j leq r_j)。当然,我们会贪心地选择这个区间上最左边的合法的(j)。并且,只要某个(j)在当前中心为(i)的时候满足(i - j leq r_j),那么,对于所有(k leq i),都有(k - j leq r_j)。
于是,只要从左往右移动(i),每次删除移动后非法的(j),问题就变成实现一个数据结构,支持:
- 删除一个元素
- 求一个值后继
这可以轻易地以(O(n log n))的时间复杂度实现,但我们希望一个更优的算法。
考虑一个元素如果每删除了,那么受影响的就只有以它为后继的值。这些值的后继会变成它们后继的后继。设(S_i)表示以元素(i)为后继的所有值的集合,那么,删除(i)后,我们只要把(S_i)合并到(S_j)里面就可以了。(元素(j)是元素(i)的后继)
因此,我们用并查集维护一下就好了。这个巧妙的技巧应该会在本题卡常时发挥作用。
时间复杂度(O(n alpha(n)))
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n,len[N * 2],r[N];
char s[N];
void manacher() {
for (int i = 2 ; i <= (n << 1) ; ++ i) {
int l = i >> 1, r = (i + 1) >> 1;
while (s[l - len[i]] == s[r + len[i]] && l - len[i] >= 1 && r + len[i] <= n)
++ len[i];
l = i - 1, r = i + 1;
while ((l >> 1) - len[l] > (i >> 1) - len[i] && l >= 1 && r <= 2 * n)
len[r++] = len[l--];
len[r] = ((i + 1) >> 1) + len[i] - ((r + 1) >> 1);
i = r - 1;
}
}
int uni[N],ans;
vector<int> vec[N];
int getfa(int pos) {
return pos == uni[pos] ? pos : uni[pos] = getfa(uni[pos]);
}
int main() {
scanf("%d",&n);
scanf("%s",s+1);
manacher();
for (int i = 1 ; i <= n ; ++ i)
r[i] = len[i << 1 | 1];
for (int i = 1 ; i <= n ; ++ i)
uni[i] = i;
for (int i = 1 ; i < n ; ++ i) {
int k = i - (r[i] >> 1);
ans = max(ans,4 * (i - getfa(k)));
vec[i + r[i]].push_back(i);
for (int j = 0 ; j < (int)vec[i].size() ; ++ j)
uni[vec[i][j]] = vec[i][j] + 1;
}
printf("%d
",ans);
return 0;
}
小结:对一些简单的小技巧还是不熟悉。或许是平庸的大数据结构做多了的问题吧。做题策略要调整。