定义
奇根:长度为-1的回文串所在节点(为了便于处理)
偶根:长度为0的回文串所在节点
:当前节点维护的回文长度
:转移函数,注意其中所有状态均为回文(单次转移相当于节点代表字符串左右两边加上)
:失配指针,指向最大匹配后缀回文的所在节点
前置理论
一个长度为的字符串中,本质不同的回文子串个数也不超过
对于在一个串中新增一个字符的情况下,本质不同回文子串个数最多新增1个
证明:
考虑增量过程中新增的字符,新增的回文肯定跟它有关(也就是肯定是的形式,任取)
我们假设真的存在新增多个本质不同回文子串,从中挑出最长的一个串
可以发现其它的回文子串即使存在也是以的后缀形式存在
但作为回文,后缀出现过的,那么前缀也肯定出现过,因此是属于本质相同的子串,END
因此我们可以用个状态转移来表示所有本质不同的回文子串
构造过程
- 偶根的失配指针指向奇根,奇根必然不会失配(单个字符也能形成回文串)
- 采用增量的方法一个一个添加字符,假设现在添加字符
- 维护后缀的最长回文字串,任取,设所在节点为
- 通过不断地寻找的
suffix-link
(),相当于缩短后缀,找到一个回文串,使得满足在原串中符合形式的回文串,设节点为 - 如果存在,那我们不必理会,因为由状态存在得知是本质相同的(前面存在过的);否则新增状态,我们已知其长度就是多两个字符的长度(这个时候奇根直接+2就是巧妙的形成单个字符的回文串)
- 至于,可以发现同样是同样是的后缀加上,直接继续在的
suffix-link
上寻找符合的状态即可,设为,令
好了已经构造完了,由于找的过程会使得中的不断右移,因此总体来看,其成本还是
完成版
#include <bits/stdc++.h>
struct PT {
const std::string &str;
std::vector<std::array<int, 127>> trans;
std::vector<int> fail;
std::vector<int> len;
int odd, even;
int last;
int make(int f, int l) {
int node = trans.size();
trans.push_back({});
fail.emplace_back(f);
len.emplace_back(l);
return node;
}
PT(const std::string &str)
: str(str),
odd(make(0, -1)),
even(make(0, 0)),
last(even) {
for(int i = 0; i < str.size(); ++i) {
add(i);
}
}
void add(int index) {
int c = str[index];
int u = last;
int v = failwalk(u, index);
if(!trans[v][c]) {
int f, l;
if(len[v] == -1) {
f = even;
l = 1;
} else {
int w = failwalk(fail[v], index);
f = trans[w][c];
l = len[v] + 2;
}
int n = make(f, l);
trans[v][c] = n;
}
last = trans[v][c];
}
int failwalk(int u, int index) {
for(;;) {
int lo = index - 1 - len[u];
if(lo >= 0 && str[lo] == str[index]) break;
u = fail[u];
}
return u;
}
};
int main() {
std::string str = "aabaaa";
PT pt(str);
std::cout << *std::max_element(pt.len.begin(), pt.len.end()) << std::endl;
return 0;
}
参考
http://adilet.org/blog/palindromic-tree/