【算法笔记+BZOJ3676】回文串(回文树/回文自动机模版题)

题目地址:https://www.lydsy.com/JudgeOnline/problem.php?id=3676

回文树笔记:


字符串:abbaabba,下标从1开始存,建树如下:

节点0下面的都是偶数长度的回文串,节点1下面的都是奇数长度的回文串。

每个节点i(除0/1)表示1->(i-1)内以s[i-1]结尾的最长回文串,简记节点i表示的回文串,i为节点编号

  • s[k]:存第k个插入的字符
  • len[i] : 1->(i-1)内以s[i-1]结尾的最长回文串长度,特别的,len[0]=0, len[1]=-1
  • cnt[i] : 记 1->(i-1)内以s[i-1]结尾的最长回文串为str, cnt[i]表示整个字符串中str出现的次数
  • num[i] : 1->(i-1)内有多少个不同的回文串
  • fail[i] : i“失配” 后指向的位置,在i的父节点f[i]表示的回文串左右两侧添加c字符可得到新的回文串,在fail[i]的父节点f[fail[i]]的表示的回文串的两侧添加c字符也可得到新的回文串,等价于s[n-len[f[i]]-1]=s[n] , s[n-len[f[fail[i]]]-1]=s[n],n为当前已经插入的字符数目。且节点fail[i]表示的回文串是 节点i表示的回文串的最长后缀回文串,以s[i-1]结尾。如上图节点9表示的回文串是abbaabba,fail[9]=5,节点5表示的回文串是abba,是abbaabba的最长后缀回文串
  • nxt[i][c] : 记录节点i表示的回文串左右两侧添加字符c形成的新回文串,该新的回文串对应的节点编号

题目:


Description

考虑一个只包含小写拉丁字母的字符串s。我们定义s的一个子串t的“出 
现值”为t在s中的出现次数乘以t的长度。请你求出s的所有回文子串中的最 
大出现值。 

Input

输入只有一行,为一个只包含小写字母(a -z)的非空字符串s。 

Output


输出一个整数,为逝查回文子串的最大出现值。 

Sample Input

【样例输入l】
abacaba

【样例输入2]
www

 

Sample Output

【样例输出l】
7

【样例输出2]
4

ac代码:


#include <bits/stdc++.h>
using namespace std;
const int maxn = 300005;
typedef long long ll;
int fail[maxn], cnt[maxn], num[maxn], len[maxn], nxt[maxn][26], s[maxn];
int last, n, p;
char ss[maxn];
int newnode(int l) //l是节点对应的最长回文子串的长度
{
    for(int i = 0; i < 26; i++)
        nxt[p][i] = 0;
    len[p] = l;
    cnt[p] = 0;
    num[p] = 0;
    return p++;
}
void init()
{
    p = 0, last = 0, n = 0, s[0] = -1;
    newnode(0); // p=0的初始化
    newnode(-1); // p = 1的初始化,之后 p = 2
    fail[0] = 1;
    fail[1] = 0;
}
int get_fail(int x)
{
    while(s[n - len[x] - 1] != s[n])
        x = fail[x];
    return x;
}
void insert(int c) // c = ch - 'a'
{
    s[++n] = c;
    int cur = get_fail(last);
    if(!nxt[cur][c])
    {
        int now = newnode(len[cur] + 2); //now表示当前节点编号
        fail[now] = nxt[get_fail(fail[cur])][c];//此句和下面一句顺序一定不能倒!!
        //因为nxt初始化为0,若当前节点表示的回文串是它自己,那么它的fail应该指向0,此时cur=1,get_fail(fail[cur])=1
        nxt[cur][c] = now;
        num[now] = num[fail[now]] + 1; //1->n所能形成的回文串数目+1
    }
    last = nxt[cur][c];//当前节点编号
    cnt[last]++;//以last为结尾的最长回文子串对应的数目+1
}
void count()
{
    for(int i = p - 1; i >= 0; i--) //从后往前更新
        cnt[fail[i]] += cnt[i];
}
int main()
{
    init();
    scanf("%s", ss+1);//为了避免MLE,也可以边读字符边insert
    int lenn = strlen(ss+1);
    for(int i = 1; i <= lenn; i++)
        insert(ss[i] - 'a');
    count();
    ll ans = 0;
    for(int i = 0; i <= p - 1; i++)
        ans = max(ans, 1LL * len[i] * cnt[i]);
    printf("%lld\n", ans);
    return 0;
}

【参考博客】:

https://blog.csdn.net/weixin_41190227/article/details/87271574

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值