题面
考虑一个只包含小写拉丁字母的符串 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;
}