1. 程式人生 > 實用技巧 >【洛谷P4287】雙倍迴文

【洛谷P4287】雙倍迴文

題目

題目連結:https://www.luogu.com.cn/problem/P4287
記字串\(w\)的倒置為\(w^R\)。例如\((abcd)^R=dcba\)\((abba)^R=abba\)
對字串x,如果\(x\)滿足\(x^R=x\),則稱之為迴文;例如abba是一個迴文,而abed不是。
如果x能夠寫成的\(ww^Rww^R\)形式,則稱它是一個“雙倍迴文”。換句話說,若要\(x\)是雙倍迴文,它的長度必須是\(4\)的倍數,而且\(x\)\(x\)的前半部分,\(x\)的後半部分都要是迴文。例如\(abbaabba\)是一個雙倍迴文,而\(abaaba\)不是,因為它的長度不是4的倍數。
\(x\)

的子串是指在\(x\)中連續的一段字元所組成的字串。例如\(be\)\(abed\)的子串,而\(ac\)不是。
\(x\)的迴文子串,就是指滿足迴文性質的\(x\)的子串。
\(x\)的雙倍迴文子串,就是指滿足雙倍迴文性質的\(x\)的子串。
你的任務是,對於給定的字串,計算它的最長雙倍迴文子串的長度。
\(n\leq 500000\)

思路

不難發現,必然存在一個最長的雙倍迴文子串,其結束為止為 \(x\),且以 \(x\) 結尾的最長迴文子串的長度等於最長雙倍迴文子串的長度。
假設不存在這樣一個位置,那麼顯然最長雙倍迴文子串的長度要小於最長迴文子串的長度。不妨設所有最長雙倍迴文子串中,結束位置最前的在 \(i\)

,以 \(i\) 結尾的最長迴文子串的起始位置是 \(j\),以 \(i\) 結尾的最長雙倍迴文子串的起始位置是 \(k\),那麼顯然有 \(s_{i\sim i+j-k}=s_{k\sim j}\)
所以對稱過去必然還存在一個長度等於最長雙倍迴文子串長度的雙倍迴文子串,且其位置在 \(j\) 之前,矛盾。
所以我們可以在建 PAM 的同時維護出 \(f_x\) 表示以節點 \(x\) 結束且長度不超過 \(\lfloor\frac{\mathrm{len}_x}{2}\rfloor\) 的最長迴文子串所屬節點。如果其長度為偶數且恰好等於 \(\frac{\mathrm{len}_x}{2}\)
,那麼就用 \(\mathrm{len}_x\) 更新答案。
時間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using namespace std;

const int N=500010;
int n,ans;
char s[N];

struct PAM
{
	int tot,last,ch[N][26],len[N],fail[N],f[N];
	PAM() { tot=1; fail[0]=1; len[1]=-1; }
	
	int getfail(int n,int x)
	{
		while (s[n]!=s[n-len[x]-1]) x=fail[x];
		return x;
	}
	
	void ins(int i)
	{
		int c=s[i]-'a',p=getfail(i,last);
		if (!ch[p][c])
		{
			int np=++tot;
			len[np]=len[p]+2;
			f[np]=fail[np]=ch[getfail(i,fail[p])][c];
			if (len[np]>2)
			{
				int q=f[p];
				while (s[i]!=s[i-len[q]-1] || len[ch[q][c]]*2>len[np]) q=fail[q];
				if (len[ch[q][c]]%2==0 && len[ch[q][c]]*2==len[np]) ans=max(ans,len[np]);
				f[np]=ch[q][c];
			}
			ch[p][c]=np;
		}
		last=ch[p][c];
	}
}pam;

int main()
{
	scanf("%d%s",&n,s+1);
	for (int i=1;i<=n;i++)
		pam.ins(i);
	printf("%d",ans);
	return 0;
}