字符串

本文字符串默认 1-index。

周期:若 \(p\) 对于字符串 \(s\) 满足 \(\forall i\in[1,|s|-p]\)\(s_i=s_{i+p}\) 则称 \(p\)\(s\) 的周期。

border:\(s\) 长度为 \(p\) 的 border 满足 \(s:[,p]=s:(|s|-p,]\)。根据定义有 \(|s|-p\)\(s\) 的周期。

Weak Periodicity Lemma,WPL (弱周期定理)

对于 \(s\) 的周期 \(p,q(p\ge q)\) 若满足 \(p+q\le |s|\),则 \(\gcd(p,q)\) 也为 \(s\) 的周期。

\(\forall i\in[1,|s|-p]\)\(s_i=s_{i+p}=s_{i+q}\)

\(\forall i\in(q,|s|]\)\(s_{i-q}=s_i\)

对于 \(i\in[1,|s|-p],s_i=s_{i+p}=s_{i+p-q}\)

对于 \(i\in(|s|-p,|s|],s_i=s_{i-p}=s_{i-p+q}\)

等价于 \(p-q\)\(s\) 的周期,于是根据辗转相减容易得出原定理。

1. 对于所有长度 \(\ge \frac{|s|}{2}\) 的 border 长度构成等差数列。

找到最长的 border \(p\) 与任意 border \(q\),根据 WPL 得出 \(p\)\(q\)\(\gcd(|s|-p,|s|-q)\)\(s\) 的周期。若 \(\gcd(|s|-p,|s|-q)< |s|-p\),则必定存在长度大于 \(p\) 的 border,矛盾。则 \(\gcd(|s|-p,|s|-q)=|s|-q\to (|s|-p)|(|s|-q)\)

并且根据 WPL 也能得出,所有 \(k(|s|-p)\le |s|-q\) 均为 \(s\) 的周期。得证。

2. 字符串 \(s\) 的所有 border 必定能分为 \(\log |s|\) 个不交等差数列。

上一个结论必须满足 \(p,q\ge \frac{|s|}{2}\) 的原因为 \(2|s|-p-q\le |s|\),总能使用 WPL 进一步推导。

考虑现将所有 \(len\ge \frac{|s|}{2}\) 的 border 分为一等差数列,对于剩下的部分,取出最长的 border \(p\),则对于任意 border \(q\)\(q\) 同样为 \(p\) 的 border。

[-----p-----]......[-----------]
[-q-]...[---]......[---]...[---]

\(p\) 看作原串,又转化为原问题,将 \(len\ge \frac{p}{2}\) 的 border 分出,以此类推。因为每次长度减半,故不超过 \(\log |s|\) 次。

P5287 [HNOI2019] JOJO

参考了 【题解】P5287 [HNOI2019] JOJO

自己跟着研究一下貌似也没有想的那么难。

首先容易转化为 \(\sum |border|\)

允许离线的撤销操作可以看做建出操作树,随后 dfs 一遍求出每个点依次进行根到该点路径上操作后的答案即可。

\(|s|\) 有 1e9 的量级,观察数据范围,正解肯定与段数有关。考虑在字符串后插入一段如何求出其 border 长度和。

\(C_i\)\(l_i\) 分别为第 \(i\) 段对应字符、长度。\(sum_i\) 为前 \(i\) 段的总长度。下文下标中也会出现段的命名而不是段的标号,表达是一样的。

因为题目有保证相邻两段字符不同,由此能容易得出性质:

若第 \(i\) 段 border 从第 \(i-1\) 段 border 更新 \(p\) 得来,则 \(p\) 必定为某段的右端点。

于是每段记录以某段右端点结尾的 border \(nxt\)。有暴力思路,每次暴力跳 \(nxt\),若下一段的字符 \(c\) 与当前段字符相同,则贡献形如等差数列。

|--p--||--s--|    |--q--||-t-|
          ↑
          c              当前段

如上,\(len=\min(|s|,|t|)\)。则贡献即 \(t\) 开头的 \(len\) 位 border 分别对应 \(s\) 开头 \(len\) 位。从后向前 \(nxt\),则 \(sum_p\) 不断减小。则 \(t\) 中每一位最长 border 必定是在第一次被贡献。记录 \(mx\) 表示当前 \(t\) 的前 \(mx\) 位已经被贡献。则一次 \(len\) 贡献的位置为 \((mx,len]\),对应 border 长度为 \((sum_p+mx,sum_p+len]\)。容易等差数列求和计算,然后更新 \(mx\),若当前段被完全贡献了,记录 \(nxt\)

但是还是有问题,样例出现了 aabbbaaabb 则整个串的 border 应为 aabb 但因为倒数第二段没有被完整匹配过,于是无法从其 border 拓展。

需要判断在总段数 \(>2\),前一段在 border 中只匹配了一个后缀的情况,也就是 \(C_1=C_{end-1}\;\) \(\rm{and}\) \(\;C_2=C_{end}\;\)\(\rm{and}\)\(\; len_1\le len_{end-1}\) 更新方式一样。

测样例时发现又出问题了,考虑 abaa 的情况,abaa 的 border 应该与 aba 相同,但是不会被贡献。若存在未被贡献的位置,若 \(C_1=C_{end}\),则其贡献均为 \(len_1\),否则为 0。

上文的照着代码和样例玩一下应该是容易理解的。

于是有 77pts submission。貌似能卡过去。

Takanashi Rikka
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define il inline
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;}
inline int max(vector<int>w){int res=-1e9;for(int i:w)cmx(res,i);return res;}
const int mod=998244353;
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=1e5+5;
#define int ll
int nxt[N],head[N],cnt,id[N],len[N],p[N],tp,s[N],res[N],ans,inv=qp(2,mod-2),it[N];char C[N];
il int S(int l,int r){
//	cerr<<l<<' '<<r<<'\n';
	if(l>r)return 0;
	assert(l<=r);
	return (l+r)*(r-l+1)%mod*inv%mod;
}
struct edge{int to,nxt,x;char c;}e[N];
il void add(int u,int v,int x,char c){
//	cerr<<u<<' '<<v<<' '<<x<<c<<'\n';
	e[++cnt]={v,head[u],x,c},head[u]=cnt;
}
void ins(int u,int x,char c){
//	cerr<<"#"<<tp<<' '<<x<<' '<<c<<'\n';
	int mx=0;
	++tp,len[tp]=x,C[tp]=c,s[tp]=0;
	if(tp==1)s[1]=S(1,x-1),nxt[1]=0;
	else{
		for(int i=nxt[tp-1];~i;i=it[i]){
			if(C[i+1]==c){
				int tmp=min(x,max(mx,len[i+1]));
				(s[tp]+=S(p[i]+mx+1,p[i]+tmp))%=mod,mx=tmp;
				if(len[i+1]==x&&!nxt[tp])nxt[tp]=i+1;
			}
			i=nxt[i];
			if(C[i+1]==c){
				int tmp=min(x,max(mx,len[i+1]));
				(s[tp]+=S(p[i]+mx+1,p[i]+tmp))%=mod,mx=tmp;
				if(len[i+1]==x&&!nxt[tp])nxt[tp]=i+1;
			}
			if(!i)break;
		}
		if(tp>2&&len[1]<=len[tp-1]&&C[1]==C[tp-1]&&C[2]==C[tp]){
			int tmp=min(x,max(mx,len[2]));
			(s[tp]+=S(p[1]+mx+1,p[1]+tmp))%=mod,mx=tmp;
			if(len[2]==x&&!nxt[tp])nxt[tp]=2;
		}
		if(C[1]==c&&mx<x)(s[tp]+=(x-mx)*len[1]%mod)%=mod;
	}
	it[tp]=(p[tp]==2*p[nxt[tp]]-p[nxt[nxt[tp]]]?it[nxt[tp]]:nxt[tp]);
//	if(u==23)cerr<<tp<<' '<<nxt[tp]<<' '<<mx<<' '<<len[1]<<' '<<x<<' '<<C[1]<<' '<<c<<'\n';
	(ans+=s[tp])%=mod,p[tp]=p[tp-1]+len[tp];
}
void del(){(ans+=mod-s[tp])%=mod,nxt[tp]=it[tp]=0,tp--;}
void dfs(int u){
//	cerr<<"#"<<u<<'\n';
	res[u]=ans;
	int lst=ans;
	for(int i=head[u];i;i=e[i].nxt)
		ins(u,e[i].x,e[i].c),
		dfs(e[i].to),del(),
		assert(ans==lst);
}
inline void UesugiErii(){
	int n,nw=0;cin>>n;
	for(int i=1,op,x;i<=n;i++){
		char c;cin>>op>>x;
		if(op==1)
			cin>>c,add(nw,id[i]=i,x,c),nw=i;
		else nw=id[i]=id[x];
	}
	dfs(0);
	for(int i=1;i<=n;i++)
		cout<<res[id[i]]<<'\n';
}
signed main(){
	//IO();
	cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}

考虑优化,推到这步优化大概率是通过 border theorem。

根据上文 2 结论,将第 \(i-1\) 段的所有 border 分为 \(\log\) 段等差数列,而每个等差数列中公差 \(d\) 必定为字符串的一个周期,也就是实际上一个等差数列后面的位置是一样的,所以对于每个等差数列只需要看其中位置最大与次大的进行更新即可。复杂度优化至 \(O(n\log n)\)

|------||-a-||-b-||-c-|
|-----------||---||---|
|----------------||---|
|---------------------||-|

a=b=c

至于为什么要看次大,如上图最大位置后方的周期不完整,所以可能不完整包含该等差数列的贡献。

Takanashi Rikka
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define il inline
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;}
inline int max(vector<int>w){int res=-1e9;for(int i:w)cmx(res,i);return res;}
const int mod=998244353;
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=1e5+5;
#define int ll
int nxt[N],head[N],cnt,id[N],len[N],p[N],tp,s[N],res[N],ans,inv=qp(2,mod-2),it[N];char C[N];
il int S(int l,int r){
//	cerr<<l<<' '<<r<<'\n';
	if(l>r)return 0;
	assert(l<=r);
	return (l+r)*(r-l+1)%mod*inv%mod;
}
struct edge{int to,nxt,x;char c;}e[N];
il void add(int u,int v,int x,char c){e[++cnt]={v,head[u],x,c},head[u]=cnt;}
il void ins(int u,int x,char c){
	int mx=0;
	++tp,len[tp]=x,C[tp]=c,s[tp]=0;
	if(tp==1)s[1]=S(1,x-1),nxt[1]=0;
	else{
		for(int i=nxt[tp-1];~i;i=it[i]){
			if(C[i+1]==c){
				int tmp=min(x,max(mx,len[i+1]));
				(s[tp]+=S(p[i]+mx+1,p[i]+tmp))%=mod,mx=tmp;
				if(len[i+1]==x&&!nxt[tp]){nxt[tp]=i+1;break;}
			}
			i=nxt[i];
			if(C[i+1]==c){
				int tmp=min(x,max(mx,len[i+1]));
				(s[tp]+=S(p[i]+mx+1,p[i]+tmp))%=mod,mx=tmp;
				if(len[i+1]==x&&!nxt[tp]){nxt[tp]=i+1;break;}
			}
			if(!i)break;
		}
		if(tp>2&&len[1]<=len[tp-1]&&C[1]==C[tp-1]&&C[2]==C[tp]){
			int tmp=min(x,max(mx,len[2]));
			(s[tp]+=S(p[1]+mx+1,p[1]+tmp))%=mod,mx=tmp;
			if(len[2]==x&&!nxt[tp])nxt[tp]=2;
		}
		if(C[1]==c&&mx<x)(s[tp]+=(x-mx)*len[1]%mod)%=mod;
	}
	(ans+=s[tp])%=mod,p[tp]=p[tp-1]+len[tp];
	it[tp]=(p[tp]-p[nxt[tp]]==p[nxt[tp]]-p[nxt[nxt[tp]]]?it[nxt[tp]]:nxt[tp]);
}
il void del(){(ans+=mod-s[tp])%=mod,nxt[tp]=it[tp]=0,tp--;}
void dfs(int u){
	res[u]=ans;
	int lst=ans;
	for(int i=head[u];i;i=e[i].nxt)
		ins(u,e[i].x,e[i].c),
		dfs(e[i].to),del(),
		assert(ans==lst);
}
inline void UesugiErii(){
	int n,nw=0;cin>>n;
	for(int i=1,op,x;i<=n;i++){
		char c;cin>>op>>x;
		if(op==1)
			cin>>c,add(nw,id[i]=i,x,c),nw=i;
		else nw=id[i]=id[x];
	}
	dfs(0);
	for(int i=1;i<=n;i++)
		cout<<res[id[i]]<<'\n';
}
signed main(){
	//IO();
	cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}

SA

咕咕咕。

SAM

咕咕咕。

PAM

回文自动机。简略记一下。

分奇根、偶根分别处理对应长度奇偶性的回文串。PAM 中有原串所有回文子串。

建立 trie,每个节点有 fail 指针指向其最长回文后缀。考虑增量构造,已经建立了前缀 \(s:[,i-1]\) 的 PAM,则加入第 \(i\) 个字符只用考虑以 \(i\) 结尾的最长回文后缀。

....|--b--|
|----a----|

考虑不为最长的回文后缀 \(b\),关于 \(a\) 的回文中心对称后必定会在之前出现过。而最长回文后缀必定由 \(s:[,i-1]\) 的一回文后缀两边拓展 \(s_i\) 得来,于是从 \(s:[,i-1]\) 的最长回文后缀开始往上跳 fail 直到该回文串前一个字符为 \(s_i\) 即可。复杂度均摊 \(O(n)\) 根据代码容易理解。

Takanashi Rikka
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;}
inline int max(vector<int>w){int res=-1e9;for(int i:w)cmx(res,i);return res;}
const int mod=998244353;
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=5e5+5;
char s[N];int n,lst;
struct PAM{
	int lst=0,cnt=1;
	struct node{int len,fa,e[26],cnt;}t[N];
	inline int get(int u,int i){for(;i-t[u].len-1<=0||s[i-t[u].len-1]!=s[i];u=t[u].fa);return u;}
	inline int ins(int p){
		int c=s[p]-'a',pos=get(lst,p);
		if(!t[pos].e[c]){
			int cur=++cnt;
			t[cur].fa=t[get(t[pos].fa,p)].e[c],
			t[pos].e[c]=cur,
			t[cur].len=t[pos].len+2,
			t[cur].cnt=t[t[cur].fa].cnt+1;
		}
		return t[lst=t[pos].e[c]].cnt;
	}
}P;
inline void UesugiErii(){
	cin>>(s+1);n=strlen(s+1);
	P.t[0].fa=1,P.t[1].len=-1;
	for(int i=1;i<=n;i++){
		if(i>1)s[i]=(s[i]-'a'+lst)%26+'a';
		cout<<(lst=P.ins(i))<<' ';
	}
}
signed main(){
	//IO();//cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}

P3649 [APIO2014] 回文串

与板子基本一致,一个回文串对出现次数的贡献为其对应节点在 fail 树上到根链上 +1,容易 dfs 得到并更新答案。

Takanashi Rikka
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define il inline
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;} 
inline int max(vector<int>w){int res=-1e9;for(int i:w)cmx(res,i);return res;}
const int mod=998244353;
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=3e5+5;
#define int ll
char s[N];int n,ans;
struct PAM{
	int tmp,lst,cnt=1;
	struct node{int fa,len,e[26],cnt;}t[N];
	int get(int u,int p){for(;(tmp=p-t[u].len-1)<=0||s[tmp]!=s[p];u=t[u].fa);return u;}
	void ins(int p){
		int c=s[p]-'a',pos=get(lst,p);
		if(!t[pos].e[c]){
			int cur=++cnt;
			t[cur].fa=t[get(t[pos].fa,p)].e[c]; 
			t[cur].len=t[pos].len+2,
			t[pos].e[c]=cur;
		}
		return ++t[lst=t[pos].e[c]].cnt,void();
	}
	void calc(){
		for(int i=cnt;i>=2;i--)
			t[t[i].fa].cnt+=t[i].cnt;
		for(int i=2;i<=cnt;i++)
			cmx(ans,t[i].len*t[i].cnt);
	}
}P;
inline void UesugiErii(){
	cin>>(s+1);n=strlen(s+1);
	P.t[0].fa=1,P.t[1].len=-1;
	for(int i=1;i<=n;i++)P.ins(i);
	P.calc();
	cout<<ans;
}
signed main(){
	//IO();//cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}

P4287 [SHOI2011] 双倍回文

根据题目容易得出判断双倍回文子串的充要条件:其最长回文后缀为偶回文串且长度恰好原子串为一半。

相较 PAM 板子,多维护 \(it\) 指针表示第一个 \(len\le \frac{|s|}{2}\) 的回文后缀,用其父节点的 \(it\) 拓展即可,与 fail 的均摊类似,复杂度线性。再根据充要条件判断更新答案即可。

Takanashi Rikka
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define fr(x) fin(x),fout(x);
#define Fr(x,y) fin(x),fout(y)
#define INPUT(_1,_2,FILE,...) FILE
#define IO(...) INPUT(__VA_ARGS__,Fr,fr)(__VA_ARGS__)
using namespace std;
using namespace __gnu_pbds;
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define il inline
#define cfast ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define ll long long
#define ull unsigned long long
#define intz(x,y) memset((x),(y),sizeof((x)))
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define tup(x) array<int,(x)>
inline ll read(){
    ll x=0,f=1;char ch=nc();
    while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}
    while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();
   	return x*f;
}
//void write(int x){cout<<x<<' ';}
//void write(pii x){cout<<"P("<<x.fi<<','<<x.se<<")\n";}
//void write(vector<auto>x){for(auto i:x)write(i);cout<<'\n';}
//void write(auto *a,int l,int r){for(int i=l;i<=r;i++)write(a[i]);cout<<'\n';}
inline ll lowbit(ll x){return x&-x;}
#define pcount(x) __builtin_popcount(x)
inline void cmx(auto &x,ll y){if(y>x)x=y;}
inline void cmn(auto &x,ll y){if(y<x)x=y;}
inline int max(vector<int>w){int res=-1e9;for(int i:w)cmx(res,i);return res;}
const int mod=998244353;
ll qp(ll x,int y){ll res=1;for(;y;x=x*x%mod,y>>=1)if(y&1)res=res*x%mod;return res;}
const int N=5e5+5;
char s[N];int n,ans;
struct PAM{
	struct node{int len,e[26],fa,it;}t[N];
	int tmp,lst,cnt=1;
	int get(int u,int i){for(;(tmp=i-t[u].len-1)<=0||s[tmp]!=s[i];u=t[u].fa);return u;}
	void ins(int p){
		int c=s[p]-'a',pos=get(lst,p);
		if(!t[pos].e[c]){
			int cur=++cnt;
			t[cur].fa=t[get(t[pos].fa,p)].e[c];
			t[cur].len=t[pos].len+2,
			t[pos].e[c]=cur;
			if(t[cur].len<=2)
				t[cur].it=t[cur].fa;
			else{
				int x=t[pos].it;
				for(;s[p-t[x].len-1]!=s[p]||(t[x].len+2<<1)>t[cur].len;x=t[x].fa);
				t[cur].it=t[x].e[c];
			}
		}
		lst=t[pos].e[c];
	}
	il void calc(){
		for(int i=2;i<=cnt;i++)
			((t[t[i].it].len<<1)==t[i].len&&!(t[t[i].it].len&1)?(cmx(ans,t[i].len),1):0);
	}
}P;
inline void UesugiErii(){
	cin>>n>>(s+1);
	P.t[0].fa=1,P.t[1].len=-1;
	for(int i=1;i<=n;i++)P.ins(i);
	P.calc(),cout<<ans;
}
signed main(){
	//IO();//cfast;
	int _=1;//cin>>_;
	for(;_;_--)UesugiErii();
	return 0;
}
posted @ 2025-12-30 16:51  Uesugi1  阅读(7)  评论(0)    收藏  举报