jzoj3654 【APIO2014】回文串(palindrome) (回文树、自动机)

题面

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

回文树裸题

回文树: 一个点代表一种内容相同的回文串。 转移表示在此回文串前后缀加字母。
fail指向当前回文串的最长回文后缀。
last是当前加入完毕的总串的最长回文后缀所在点。 (为了接下一个字母)

题目要求为长度 * 出现次数最大值。
一开始维护的cnt是这种回文串的出现次数总和。

codetrick:
0作为偶根,1作为奇根,fail[0]=1,fail[1]=-1(方便判无回文后缀)
len[1]=-1,方便处理奇数长度情况。

复杂度

最多n个本质不同回文串。由回文树构造方法可知。 (马拉车过程也可说明)

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N=3e5+10;
char s[N];
int n,last,len[N],fail[N],cnt[N],p,nex[N][26];

int main() {
    freopen("palindrome.in","r",stdin);
//  freopen("palindrome.out","w",stdout);
    scanf("%s",s+1); n=strlen(s+1);
    p=1; len[1]=-1; fail[0]=1; fail[1]=-1;
    //要记住的Trick!
    //1 odd root
    for (int i=1; i<=n; i++) {
        while (s[i-len[last]-1] != s[i]) //寻找能接上的最长回文后缀
            last=fail[last];
        int &g=nex[last][s[i]-'a'];
        if (!g) { //若不存在新建节点与fail
            g=++p,len[g]=len[last]+2;
            int k=fail[last];
            while (k!=-1 && s[i-len[k]-1] != s[i]) k=fail[k];
            //维护fail
            if (k==-1) fail[g]=0; //如没有是指向空串的。
            else fail[g]=nex[k][s[i]-'a'];
        }
        cnt[g]++; //其实g的fail链上出现次数全部+1了,因此后面再扫一遍统计。相当于打tag。
        last=g;
    }
    for (int i=p; i>=0; i--) cnt[fail[i]]+=cnt[i];
    long long ans=0;
    for (int i=1; i<=p; i++) ans=max(ans,(long long)len[i]*cnt[i]);
    cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/79209842
今日推荐