回文自动机例题

今天学习了一下回文自动机,吊打\(manacher\)有没有(除了空间
回文自动机基于这两个性质:
1.一个长度为\(n\)的字符串的本质不同回文子串是\(O(n)\)级别的
2.在一个字符串后增加一个字符后,最多新增\(1\)个本质不同回文子串
这两条性质都可以用归纳法证明
于是我们想到用一个结点来代表一个本质不同的回文串,然后就有\(PAM\)了。和\(AC\)自动机一样,\(PAM\)也有\(fail\)失配边和\(ch\)转移边,\(fail\)代表的是最长回文后缀
采用增量构造法来构建\(PAM\),在当前字符串末尾插入一个字符\(c\),如果出现了一个新的回文子串,就新建一个结点,并计算其失配边
另外最开始有两个结点,分别代表奇数长度的根结点和偶数长度的根结点
代码如下(细节看注释):

#define N 100000

int last, nid;
int ch[N+5][26], fail[N+5], len[N+5], cnt[N+5];

void init() {
  fail[0] = fail[1] = nid = 1; //偶数的根结点为0,fail指向1;奇数为1,fail指向自己
  len[1] = -1; //因为是奇数,所以不是0
}

void build(char *s) {
  int n = strlen(s+1);
  for(int i = 1; i <= n; ++i) {
    int p = last, c = s[i]-'a';
    while(s[i-len[p]-1] != s[i]) p = fail[p]; //直到能够形成回文串
    if(!ch[p][c]) { //新的本质不同回文串
      int v = ++nid, t = fail[p]; //把t设为fail[p]是因为后缀不能是它本身
      len[v] = len[p]+2;
      while(s[i-len[t]-1] != s[i]) t = fail[t]; //跳fail找最长回文后缀
      fail[v] = ch[t][c]; //跟AC自动机有点像
      ch[p][c] = v;
    }
    last = ch[p][c]; //更新last
    cnt[last]++;
  }
}

然后[APIO2014]回文串就变成板子了:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

#define N 300000

int last, nid;
int ch[N+5][26], fail[N+5], len[N+5], cnt[N+5];
long long ans = 0;

void init() {
  fail[0] = fail[1] = nid = 1;
  len[1] = -1;
}

void build(char *s) {
  int n = strlen(s+1);
  for(int i = 1; i <= n; ++i) {
    int p = last, c = s[i]-'a';
    while(s[i-len[p]-1] != s[i]) p = fail[p];
    if(!ch[p][c]) {
      int v = ++nid, t = fail[p];
      len[v] = len[p]+2;
      while(s[i-len[t]-1] != s[i]) t = fail[t];
      fail[v] = ch[t][c];
      ch[p][c] = v;
    }
    last = ch[p][c];
    cnt[last]++;
  }
}

void topo() {
  queue<int> q;
  int in[N+5] = {0};
  for(int i = 0; i <= nid; ++i) in[fail[i]]++;
  for(int i = 0; i <= nid; ++i) if(!in[i]) q.push(i);
  while(!q.empty()) {
    int u = q.front(); q.pop();
    ans = max(ans, 1LL*len[u]*cnt[u]);
    in[fail[u]]--, cnt[fail[u]] += cnt[u];
    if(!in[fail[u]]) q.push(fail[u]);
  }
}

int main() {
  init();
  char s[N+5] = {0};
  scanf("%s", s+1);
  build(s);
  topo();
  printf("%lld\n", ans);
  return 0;
}

还有一道最长双回文串

posted @ 2019-04-23 16:49  dummyummy  阅读(342)  评论(0编辑  收藏  举报