P3649 [APIO2014] 回文串

题目链接

P3649 [APIO2014] 回文串

[APIO2014] 回文串

题目描述

给你一个由小写拉丁字母组成的字符串 s。我们定义 s 的一个子串的存在值为这个子串在 s 中出现的次数乘以这个子串的长度。

对于给你的这个字符串 s,求所有回文子串中的最大存在值。

输入格式

一行,一个由小写拉丁字母(a~z)组成的非空字符串 s

输出格式

输出一个整数,表示所有回文子串中的最大存在值。

样例 #1

样例输入 #1

abacaba

样例输出 #1

7

样例 #2

样例输入 #2

www

样例输出 #2

4

提示

【样例解释1】

|s| 表示字符串 s 的长度。

一个字符串 s1s2s|s| 的子串是一个非空字符串 sisi+1sj,其中 1ij|s|。每个字符串都是自己的子串。

一个字符串被称作回文串当且仅当这个字符串从左往右读和从右往左读都是相同的。

这个样例中,有 7 个回文子串 a,b,c,aba,aca,bacab,abacaba。他们的存在值分别为 4,2,1,6,3,5,7

所以回文子串中最大的存在值为 7

第一个子任务共 8 分,满足 1|s|100

第二个子任务共 15 分,满足 1|s|1000

第三个子任务共 24 分,满足 1|s|10000

第四个子任务共 26 分,满足 1|s|100000

第五个子任务共 27 分,满足 1|s|300000

解题思路

回文树

回文树即回文自动机,类似于后缀自动机,具有转移边和后缀链,转移边按字符左右扩展,即在当前回文子串的基础上向左右各扩展一个一样的字符,后缀链即为当前回文子串的不包括本身的最长后缀回文子串,注意,回文串分为奇数和偶数,故需要建立两个根,奇根通过转移边连向所有长度为奇数的回文子串表示的状态节点,偶根通过转移边连向所有长度为偶数的回文子串表示的状态节点,一开始,偶根长度为 0,奇根长度为 1因为长度每次通过转移边转移时其长度都要增加 2,另外偶根的后缀链要连向奇根,不妨这样理解:一个偶数的回文子串的长度的后缀链连向的节点表示的回文子串的长度至少要减少 1,而偶根表示的是一个空串,其后缀链至少应该连向长度为 1 的回文子串表示的节点,即奇根。这样除了奇根,所有的节点有且仅有一条后缀链,即回文自动机本身也是由转移边形成的 DAG 和后缀链形成的树组成。类似于后缀自动机,采取增量构造的方式,假设当前已经构造好了 p1 个字符的回文自动机,现在向回文自动机增加一个 s[p] 的字符,从上一个字符结尾的最长回文子串的节点开始,不断沿着后缀链走,直到 s[p]=s[plen1],即在此回文子串的前面有一个字符 s[p],这样在该字符左右添加 s[p] 这个字符即为 p 这个位置结尾的最长回文子串,如下:
image
此时找到了 A 这个结束状态的子串,如果 XAX 不存在回文自动机中的话再建立该字串表示的状态节点,现在的问题在于该状态节点的后缀链该如何指向,即对于 A 这个状态节点来说,由于后缀链指向的节点不能指向自己,即开始 A 应该先走向其后缀链表示的节点,然后再沿着后缀链走,直到该状态节点表示的回文子串的前面一个字符为 X,此时在该回文子串上通过转移边 X 即得 XBX,即为 XAX 这个回文子串表示的状态节点得后缀链指向的状态节点

另外,需要注意的一点,对于一个字符串 s 而言,其本质不同的回文子串最多只有 |s| 个,证明略

本题要求某个回文子串的出现次数乘以其长度的最大值,主要难点在于统计回文子串的出现次数上,类似于后缀自动机,即由于 DAG 本身就是一个拓扑图,因为通过反向转移边,长度长的回文子串一定包含长度短的回文子串,故从后往前递推统计即可

  • 时间复杂度:O(n)

代码

// Problem: P3649 [APIO2014] 回文串 // Contest: Luogu // URL: https://www.luogu.com.cn/problem/P3649 // Memory Limit: 125 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=3e5+5; char s[N]; namespace pam { int sz,tot,lst; int cnt[N],ch[N][26],len[N],fail[N]; char s[N]; int node(int l) { sz++; memset(ch[sz],0,sizeof ch[sz]); len[sz]=l; fail[sz]=cnt[sz]=0; return sz; } void clear() { sz=-1; lst=0; s[tot=0]='$'; node(0); node(-1); fail[0]=1; } int getfail(int x) { while(s[tot-len[x]-1]!=s[tot])x=fail[x]; return x; } void insert(char c) { s[++tot]=c; int now=getfail(lst); if(!ch[now][c-'a']) { int x=node(len[now]+2); fail[x]=ch[getfail(fail[now])][c-'a']; ch[now][c-'a']=x; } lst=ch[now][c-'a']; cnt[lst]++; } LL solve() { LL res=0; for(int i=sz;i>=0;i--)cnt[fail[i]]+=cnt[i]; for(int i=1;i<=sz;i++)res=max(res,(LL)len[i]*cnt[i]); return res; } } int main() { pam::clear(); scanf("%s",s+1); for(int i=1;s[i];i++)pam::insert(s[i]); printf("%lld",pam::solve()); return 0; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/16978052.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示