P3649 [APIO2014] 迴文串
題目連結
P3649 [APIO2014] 迴文串
[APIO2014] 迴文串
題目描述
給你一個由小寫拉丁字母組成的字串 \(s\)。我們定義 \(s\) 的一個子串的存在值為這個子串在 \(s\) 中出現的次數乘以這個子串的長度。
對於給你的這個字串 \(s\),求所有迴文子串中的最大存在值。
輸入格式
一行,一個由小寫拉丁字母(a~z)組成的非空字串 \(s\)。
輸出格式
輸出一個整數,表示所有迴文子串中的最大存在值。
樣例 #1
樣例輸入 #1
abacaba
樣例輸出 #1
7
樣例 #2
樣例輸入 #2
www
樣例輸出 #2
4
提示
【樣例解釋1】
用 \(\lvert s \rvert\)
一個字串 \(s_1 s_2 \dots s_{\lvert s \rvert}\) 的子串是一個非空字串 \(s_i s_{i+1} \dots s_j\),其中 \(1 \leq i \leq j \leq \lvert s \rvert\)。每個字串都是自己的子串。
一個字串被稱作迴文串當且僅當這個字串從左往右讀和從右往左讀都是相同的。
這個樣例中,有 \(7\) 個迴文子串 a,b,c,aba,aca,bacab,abacaba。他們的存在值分別為 \(4, 2, 1, 6, 3, 5, 7\)。
所以迴文子串中最大的存在值為 \(7\)。
第一個子任務共 8 分,滿足 \(1 \leq \lvert s \rvert \leq 100\)
第二個子任務共 15 分,滿足 \(1 \leq \lvert s \rvert \leq 1000\)。
第三個子任務共 24 分,滿足 \(1 \leq \lvert s \rvert \leq 10000\)。
第四個子任務共 26 分,滿足 \(1 \leq \lvert s \rvert \leq 100000\)。
第五個子任務共 27 分,滿足 \(1 \leq \lvert s \rvert \leq 300000\)。
解題思路
迴文樹
迴文樹即迴文自動機,類似於字尾自動機,具有轉移邊和字尾鏈,轉移邊按字元左右擴充套件,即在當前迴文子串的基礎上向左右各擴充套件一個一樣的字元,字尾鏈即為當前迴文子串的不包括本身的最長字尾迴文子串,注意,迴文串分為奇數和偶數,故需要建立兩個根,奇根通過轉移邊連向所有長度為奇數的迴文子串表示的狀態節點,偶根通過轉移邊連向所有長度為偶數的迴文子串表示的狀態節點,一開始,偶根長度為 \(0\)
此時找到了
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;
}