洛谷4287:双倍回文
前言:
题目描述:
- 记字符串(w)的倒置为(w^R)。例如((abcd)^R=dcba)。
- 如果一个字符串能够表示成(ww^Rww^R)的形式,责成他是一个双倍回文。
- 给定一个字符串,求最长双倍回文子串长度。
输入描述:
- 输入分为两行,第一行输入一个整数表示字符串的长度。
- 第二行输入只有英文小写的字符串。
输出描述:
- 输出一个整数表示答案,如果不存在双倍回文子串,则输出0。
思路:
- 回文自动机模板题:(trans)指针的使用。
- (trans)指针:小于等于当前节点长度一半的最长回文后缀,求法和(fail)指针类似。
- 当新建一个节点后,如果他的长度小于等于2,那么这个结点的(trans)指针指向他的(fail)节点。
- 否则的话,我们从他的父亲的(trans)指针指向的节点开始跳(fail)指针。
- 直到跳到某个结点所表示的回文串的两侧都能拓展这个字符且拓展后的长度小于等于当前节点长度的一半。
- 那么新建节点的(trans)指针就指向该节点的儿子。
- 考虑一个字符串满足双倍回文:
- 当且仅当他的(trans)指针指向的节点所表示的回文串长度恰好是这个字符串长度的一半,并且这个(trans)指针指向的节点所表示的回文串长度为偶数。
- 枚举每个节点,不停更新答案。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
int cnt; //节点个数
int last; //上一个结点
int trie[maxn][30]; //字典树
int len[maxn]; //当前节点的回文串长度
int fail[maxn]; //当前节点的fail指针
int len_str; //字符串的长度
char s[maxn]; //原字符串
int trans[maxn]; //trans指针表示<=当前节点长度一半的最长回文后缀
inline int get_fail(int las, int i)
{
while(s[i - len[las] - 1] != s[i])
las = fail[las];
return las;
}
void init()
{
cnt = 1, last = 0;
len[0] = 0, len[1] = -1;
fail[0] = 1, fail[1] = 0;
}
void build_PAM()
{
for(int i = 1; i <= len_str; i++)
{
int p = get_fail(last, i);
if(!trie[p][s[i]-'a'])
{
len[++cnt] = len[p] + 2;
fail[cnt] = trie[get_fail(fail[p], i)][s[i]-'a'];
trie[p][s[i]-'a'] = cnt;
///------------顺带求出trans指针
if(len[cnt] <= 2) trans[cnt] = fail[cnt];
///这一题数据较水 博主在这里一开始cnt写的是p
///但是没有被卡掉
///如果他的长度<=2,那么当前节点的trans指向他的fail节点
else
{
int tmp= trans[p];
while((s[i - len[tmp] - 1] != s[i]) || (((len[tmp] + 2)<<1) > len[cnt]))
tmp = fail[tmp];
//开始跳fail指针
//直到跳到某一个节点所表示的回文串两侧都能拓展这个字符
//并且拓展后的长度小于等于当前节点长度的一半.
trans[cnt] = trie[tmp][s[i]-'a'];
}
///------------结束
}
last = trie[p][s[i]-'a'];
}
}
int main()
{
scanf("%d", &len_str);
scanf("%s", s + 1); s[0] = '#';
init(); build_PAM();
int ans = 0;
for(int i = 2; i <= cnt; i++) //枚举所有的节点
if((len[trans[i]]<<1)==len[i] && len[trans[i]] % 2 == 0)
ans = max(ans, len[i]);
cout << ans << endl;
return 0;
}