回文自动机
以 i i i为尾节点的字符串维护
增加一个节点后
即为在原有的所有真后缀回文串( m a r k [ f a i l [ n u m ] ] mark[fail[num]] mark[fail[num]])
增加一个回文串更新(mark[num]
)
if (!tree[last][in[i] - 'a']) {
/*operation*/
mark[num] = mark[fail[num]] + 1;
//统计以该节点为结尾的回文串个数
}
//更新last
cnt[i]=mark[last];
回文自动机(PAM)
Interesting
本质不同的字符串总数
即为节点个数,抛去1
号节点
mark[i]=num-1
Palindromes and Super Abilities
CA Loves Palindromic
维护本质不同的字符串(信息来自于子树)
对于一个本质不同的字符串
他的子树都可以访问他
标记所有字符串,统计他所有子树信息即可
由于编号越大,层数约深,所以倒着遍历即可
for (int i = 1; i <= n; i++) {
while (in[i - len[last] - 1] != in[i]) last = fail[last];
if (!tree[last][in[i] - 'a']) {
/*operation*/
}
last = tree[last][in[i] - 'a'];
mark[last]++; //标记信息
}
for (int i = num; i; i--) {
mark[fail[i]] += mark[i];
//统计子树信息
}
P3649 回文串
拉拉队排练
维护本质不同的回文串(信息来自于父亲、祖先)
建立 f a i l fail fail树
利用 d f s dfs dfs,回溯维护回文串祖先的信息
void build() {
memset(head, 0, sizeof(int) * (num + 1)); tot = 0;
for (int i = 2; i <= num; i++) AddEdge(fail[i], i);
//建树,用fail边建树
}
int vis[maxn],res[maxn];
void dfs(int now) {
vis[len[now]]++;//维护祖先信息
/*operation 统计答案*/
for (int i = head[now]; i; i = edge[i].next)
dfs(edge[i].v); //dfs
vis[len[now]]--;//回溯
return;
}
HDU6599 I Love Palindrome String
Palindromeness
维护信息有前后两端的回文串
建立两个回文串
一个正着建,一个倒着建
对于i
节点维护i
节点正序回文串信息和i+1
节点的倒序回文串信息合并即可
struct PAM {
int tree[maxn][26];
int len[maxn], fail[maxn];
int last, num , mark[maxn];
void insert(char* in, int n) {
/*operation*/
}
}pre, suf;
int main() {
scanf("%s", in + 1); in[0] = '#';
int n = strlen(in + 1);
pre.insert(in, n);
//正向建立
reverse(in + 1, in + 1 + n);
suf.insert(in, n);
//反向建立
int ans = 0;
for (int i = 1; i < n; i++) {
ans = max(ans, pre.mark[i] + suf.mark[n-i]);
//两个串信息合并
}
}
P4287 双倍回文
Harry and magic string
前端添加节点
pre,last
两个标记维护前后添加
以pre
为例,当添加节点后
新增的节点后最长回文串不是本身时,新添加的节点不会影响last
即 l e n [ l a s t ] , f a i l [ l a s t ] len[last],fail[last] len[last],fail[last]为正确的
新增的节点后,本身为最长回文串时,要更新last
更新方法为将 l a s t = p r e last=pre last=pre
由于只统计一边,所以统计不同本质字符串个数和总共字符串个数是正确的
但维护其他信息的时候需要认真思考
以下为向前插入:
向后插入类似
void pre_insert(char* in, int i) { //向前添加方法
while (in[i + len[pre] + 1] != in[i]) pre = fail[pre];
if (!tree[pre][in[i] - 'a']) {
len[++num] = len[pre] + 2;
int j = fail[pre];
while (in[i + len[j] + 1] != in[i]) j = fail[j];
fail[num] = tree[j][in[i] - 'a'];
tree[pre][in[i] - 'a'] = num;
mark[num] = mark[fail[num]] + 1; //统计个数
}
pre = tree[pre][in[i] - 'a'];
if (len[pre] == stdr - stdl + 1) last = pre;//当本身变为回文串,更新last
sum = sum + mark[pre]; //维护总个数
}
Victor and String
前向星优化空间
当MLE
时,我们可以使用链式前向星优化
即使用链式前向星创建字典树
查询:遍历所有边,由于每个点在字典树访问次数有限,所以复杂不会超
赋值:即为建边
struct Edge{
int v;
int w; //w为字母的值
int next;
}edge[maxn];
int head[maxn], tot;
inline void AddEdge(int u, int v, int w) {
edge[++tot].v = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot;
}
int find(int now, int w) { //寻找函数
for (int i = head[now]; i; i = edge[i].next)
if (edge[i].w == w) return edge[i].v;
return 0;
}
for (int i = 1; i <= n; i++) {
while (in[i - len[last] - 1] != in[i]) last = fail[last];
int treenode = find(last, in[i] - 'a');//寻找节点
if (!treenode) {
len[++num] = len[last] + 2;
int j = fail[last];
while (in[i - len[j] - 1] != in[i]) j = fail[j];
fail[num] = find(j, in[i] - 'a');//寻找节点
AddEdge(last, num, in[i] - 'a');//建边
mark[num] = mark[fail[num]] + 1;
treenode = num; //更新节点
}
last = treenode;
cnt[i] = mark[last];
}