1. 程式人生 > 其它 >P3649 [APIO2014] 迴文串

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\) 的長度。

一個字串 \(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\)

,奇根長度為 \(-1\)\(\color{red}{為什麼這樣設定?}\)因為長度每次通過轉移邊轉移時其長度都要增加 \(2\),另外偶根的字尾鏈要連向奇根,\(\color{red}{為什麼?}\)不妨這樣理解:一個偶數的迴文子串的長度的字尾鏈連向的節點表示的迴文子串的長度至少要減少 \(1\),而偶根表示的是一個空串,其後綴鏈至少應該連向長度為 \(-1\) 的迴文子串表示的節點,即奇根。這樣除了奇根,所有的節點有且僅有一條字尾鏈,即迴文自動機本身也是由轉移邊形成的 DAG 和字尾鍊形成的樹組成。\(\color{red}{具體該如何構造?}\)類似於字尾自動機,採取增量構造的方式,假設當前已經構造好了 \(p-1\) 個字元的迴文自動機,現在向迴文自動機增加一個 \(s[p]\) 的字元,從上一個字元結尾的最長迴文子串的節點開始,不斷沿著字尾鏈走,直到 \(s[p]=s[p-len-1]\),即在此迴文子串的前面有一個字元 \(s[p]\),這樣在該字元左右新增 \(s[p]\) 這個字元即為 \(p\) 這個位置結尾的最長迴文子串,\(\color{red}{該子串表示的狀態節點的字尾鏈該如何指向?}\)如下:

此時找到了 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;
}